<!DOCTYPE HTML>
<html>
  <head>
    <meta charset="UTF-8">
    <title>SpaMo - Spaced Motif Analysis</title>
    <script type="text/javascript">
      //@JSON_VAR data
      var data = {"program":"SpaMo","version":"5.0.2","revision":"a40a52a93b6dce029c34699a737ea432925cfe33","release":"Thu Aug 30 14:26:08 2018 -0700","host":"192.168.2.3","when":"Thu Mar 31 19:29:29 2022","cmd":["spamo","-verbosity","1","-oc","meme/meme-chip_TCP7_1/spamo_out_3","-bgfile","meme/meme-chip_TCP7_1/background","-keepprimary","-primary","CCCACCCCGAATTTC","meme/meme-chip_TCP7_1/TCP7_1.meme.fa","meme/meme-chip_TCP7_1/meme_out/meme.xml","meme/meme-chip_TCP7_1/meme_out/meme.xml","meme/meme-chip_TCP7_1/dreme_out/dreme.xml"],"options":{"seq_min_hit_score":7,"margin":150,"bin_size":1,"bin_pvalue_calc_range":150,"use_best_secondary":0,"seq_max_shared_fract":0.5,"seq_odds_ratio":20,"bin_pvalue_cutoff":0.05,"motif_evalue_cutoff":10,"redundant_overlap":2,"redundant_joint":0.5,"motif_pseudocount":0.1,"bgfile":"meme/meme-chip_TCP7_1/background","motif_trim":0.25,"xalph":0,"seed":1,"bin_max":0},"alphabet":{"name":"DNA","like":"dna","ncore":4,"symbols":[{"symbol":"A","name":"Adenine","colour":"CC0000","complement":"T"},{"symbol":"C","name":"Cytosine","colour":"0000CC","complement":"G"},{"symbol":"G","name":"Guanine","colour":"FFB300","complement":"C"},{"symbol":"T","aliases":"U","name":"Thymine","colour":"008000","complement":"A"},{"symbol":"N","aliases":"X.","name":"Any base","equals":"ACGT"},{"symbol":"V","name":"Not T","equals":"ACG"},{"symbol":"H","name":"Not G","equals":"ACT"},{"symbol":"D","name":"Not C","equals":"AGT"},{"symbol":"B","name":"Not A","equals":"CGT"},{"symbol":"M","name":"Amino","equals":"AC"},{"symbol":"R","name":"Purine","equals":"AG"},{"symbol":"W","name":"Weak","equals":"AT"},{"symbol":"S","name":"Strong","equals":"CG"},{"symbol":"Y","name":"Pyrimidine","equals":"CT"},{"symbol":"K","name":"Keto","equals":"GT"}]},"background":[0.2772,0.2228,0.2228,0.2772],"sequence_dbs":[{"source":"meme/meme-chip_TCP7_1/TCP7_1.meme.fa","name":"TCP7_1.meme","last_modified":"Thu Mar 31 19:01:11 2022","loaded":3000,"excluded_too_short":3000,"excluded_no_match":0,"excluded_ambigs":0,"excluded_similar":0,"erased_primary_matches":0}],"primary_dbs":[{"source":"meme/meme-chip_TCP7_1/meme_out/meme.xml","name":"meme.xml","last_modified":"Thu Mar 31 19:27:34 2022","loaded":1,"excluded":4}],"secondary_dbs":[{"source":"meme/meme-chip_TCP7_1/meme_out/meme.xml","name":"meme.xml","last_modified":"Thu Mar 31 19:27:34 2022","loaded":5,"excluded":0},{"source":"meme/meme-chip_TCP7_1/dreme_out/dreme.xml","name":"dreme.xml","last_modified":"Thu Mar 31 19:29:23 2022","loaded":5,"excluded":0}],"secondary_motifs":[{"db":0,"id":"CCCACCCCGAATTTC","alt":"MEME-2","len":15,"ltrim":0,"rtrim":0,"nsites":869,"evalue":"6.7e-1223","pwm":[[0.0103877,0.90556,0.0690627,0.0149902],[3.18951e-05,0.963091,0.00232637,0.0345499],[3.18951e-05,0.883698,2.56357e-05,0.116245],[0.973453,2.56357e-05,0.0264896,3.18951e-05],[0.0069351,0.933175,2.56357e-05,0.059864],[0.00233263,0.974597,2.56357e-05,0.0230442],[0.00233263,0.948133,2.56357e-05,0.0495082],[0.00923684,0.909012,2.56357e-05,0.0817255],[0.188733,0.00232637,0.808909,3.18951e-05],[0.87565,2.56357e-05,0.124292,3.18951e-05],[0.996465,2.56357e-05,2.56357e-05,0.0034835],[0.0069351,0.0656111,2.56357e-05,0.927428],[3.18951e-05,2.56357e-05,2.56357e-05,0.999917],[3.18951e-05,2.56357e-05,2.56357e-05,0.999917],[3.18951e-05,0.929723,2.56357e-05,0.0702198]]},{"db":0,"id":"CTGAAACCCGRAGTAA","alt":"MEME-4","len":16,"ltrim":0,"rtrim":0,"nsites":919,"evalue":"2.4e-1350","pwm":[[0.019615,0.763815,0.00764041,0.20893],[0.00873421,2.42411e-05,0.00328789,0.987953],[0.0870717,0.00546465,0.907434,3.01599e-05],[0.996658,2.42411e-05,0.00328789,3.01599e-05],[0.999921,2.42411e-05,2.42411e-05,3.01599e-05],[0.994481,0.00111212,0.00111212,0.0032938],[0.00111804,0.8378,2.42411e-05,0.161058],[0.00547057,0.938986,2.42411e-05,0.0555191],[0.0163504,0.901993,0.00328789,0.0783676],[0.23613,0.00328789,0.76055,3.01599e-05],[0.455911,2.42411e-05,0.544035,3.01599e-05],[0.826926,0.00111212,0.171932,3.01599e-05],[0.143649,2.42411e-05,0.856297,3.01599e-05],[3.01599e-05,0.00546465,2.42411e-05,0.994481],[0.992305,2.42411e-05,0.00764041,3.01599e-05],[0.994481,2.42411e-05,0.0022,0.0032938]]},{"db":0,"id":"TGSRRGTGGGCCCCRC","alt":"MEME-1","len":16,"ltrim":0,"rtrim":0,"nsites":2826,"evalue":"6.0e-3027","pwm":[[0.106517,0.0821,0.262915,0.548468],[0.187901,0.0750232,0.556959,0.180116],[0.159947,0.258668,0.457883,0.123501],[0.555899,0.0704234,0.278483,0.0951944],[0.294054,0.0594538,0.492559,0.153932],[0.0753791,0.0332697,0.870111,0.0212401],[9.80857e-06,0.0304388,0.0187612,0.95079],[0.00991746,7.88366e-06,0.990065,9.80857e-06],[9.80857e-06,7.88366e-06,0.999972,9.80857e-06],[0.132348,0.00354676,0.616405,0.247701],[0.0824559,0.672666,0.0594538,0.185424],[9.80857e-06,0.993957,7.88366e-06,0.0060256],[9.80857e-06,0.999972,7.88366e-06,9.80857e-06],[9.80857e-06,0.981572,7.88366e-06,0.0184102],[0.516623,0.0212381,0.429222,0.0329176],[0.0180562,0.886389,0.026192,0.0693634]]},{"db":0,"id":"TTTTCGGTAAATTTT","alt":"MEME-3","len":15,"ltrim":0,"rtrim":0,"nsites":949,"evalue":"3.1e-1194","pwm":[[0.135948,0.0105594,2.34749e-05,0.853469],[2.92066e-05,2.34749e-05,0.00423803,0.995709],[2.92066e-05,0.0105594,2.34749e-05,0.989388],[0.0168874,2.34749e-05,0.00318414,0.979905],[0.0179413,0.791299,0.00423803,0.186522],[0.285563,2.34749e-05,0.699634,0.0147797],[0.040067,2.34749e-05,0.95988,2.92066e-05],[2.92066e-05,2.34749e-05,0.00529192,0.994655],[0.947242,0.00318414,0.0358467,0.0137268],[0.860844,2.34749e-05,0.102226,0.0369063],[0.930384,2.34749e-05,2.34749e-05,0.0695689],[0.233935,0.107494,2.34749e-05,0.658548],[2.92066e-05,0.013721,2.34749e-05,0.986226],[0.0010831,0.121191,2.34749e-05,0.877703],[0.00635054,0.00529192,2.34749e-05,0.988334]]},{"db":0,"id":"TYGGCATAACCTCCCT","alt":"MEME-5","len":16,"ltrim":0,"rtrim":0,"nsites":678,"evalue":"2.0e-818","pwm":[[0.00151566,3.28565e-05,3.28565e-05,0.998419],[0.00151566,0.570745,0.0044572,0.423282],[0.179955,0.00150764,0.809648,0.00888957],[0.277286,3.28565e-05,0.719691,0.00299044],[0.00888957,0.706419,0.160776,0.123917],[0.980722,0.00888155,0.00740677,0.00299044],[0.255165,3.28565e-05,0.00740677,0.737395],[0.991045,3.28565e-05,0.00888155,4.08789e-05],[0.993994,3.28565e-05,0.00298242,0.00299044],[0.00446523,0.696095,3.28565e-05,0.299407],[0.00594001,0.945321,3.28565e-05,0.0487067],[0.00446523,0.0044572,0.00150764,0.989571],[0.0782004,0.784578,3.28565e-05,0.137189],[0.00594001,0.906979,3.28565e-05,0.087049],[0.0192121,0.884858,0.0044572,0.0914724],[0.0103634,0.0354256,3.28565e-05,0.954177]]},{"db":1,"id":"AAATYCGG","alt":"DREME-2","len":8,"ltrim":0,"rtrim":0,"nsites":1129,"evalue":"4.1e-248","pwm":[[0.999936,1.97325e-05,1.97325e-05,2.45505e-05],[0.999936,1.97325e-05,1.97325e-05,2.45505e-05],[0.999936,1.97325e-05,1.97325e-05,2.45505e-05],[2.45505e-05,1.97325e-05,1.97325e-05,0.999936],[2.45505e-05,0.169181,1.97325e-05,0.830775],[2.45505e-05,0.999931,1.97325e-05,2.45505e-05],[2.45505e-05,1.97325e-05,0.999931,2.45505e-05],[2.45505e-05,1.97325e-05,0.999931,2.45505e-05]]},{"db":1,"id":"AARTTTAC","alt":"DREME-3","len":8,"ltrim":0,"rtrim":0,"nsites":668,"evalue":"4.1e-188","pwm":[[0.999892,3.33483e-05,3.33483e-05,4.14908e-05],[0.999892,3.33483e-05,3.33483e-05,4.14908e-05],[0.848716,3.33483e-05,0.151209,4.14908e-05],[4.14908e-05,3.33483e-05,3.33483e-05,0.999892],[4.14908e-05,3.33483e-05,3.33483e-05,0.999892],[4.14908e-05,3.33483e-05,3.33483e-05,0.999892],[0.999892,3.33483e-05,3.33483e-05,4.14908e-05],[4.14908e-05,0.999884,3.33483e-05,4.14908e-05]]},{"db":1,"id":"CACCYTGA","alt":"DREME-5","len":8,"ltrim":0,"rtrim":0,"nsites":689,"evalue":"2.7e-174","pwm":[[4.02264e-05,0.999887,3.2332e-05,4.02264e-05],[0.999895,3.2332e-05,3.2332e-05,4.02264e-05],[4.02264e-05,0.999887,3.2332e-05,4.02264e-05],[4.02264e-05,0.999887,3.2332e-05,4.02264e-05],[4.02264e-05,0.876538,3.2332e-05,0.123389],[4.02264e-05,3.2332e-05,3.2332e-05,0.999895],[4.02264e-05,3.2332e-05,0.999887,4.02264e-05],[0.999895,3.2332e-05,3.2332e-05,4.02264e-05]]},{"db":1,"id":"GAYTTACY","alt":"DREME-4","len":8,"ltrim":0,"rtrim":0,"nsites":743,"evalue":"1.5e-208","pwm":[[3.73032e-05,2.99825e-05,0.999895,3.73032e-05],[0.999903,2.99825e-05,2.99825e-05,3.73032e-05],[3.73032e-05,0.546389,2.99825e-05,0.453543],[3.73032e-05,2.99825e-05,2.99825e-05,0.999903],[3.73032e-05,2.99825e-05,2.99825e-05,0.999903],[0.999903,2.99825e-05,2.99825e-05,3.73032e-05],[3.73032e-05,0.999895,2.99825e-05,3.73032e-05],[3.73032e-05,0.176318,2.99825e-05,0.823614]]},{"db":1,"id":"GGGHCCM","alt":"DREME-1","len":7,"ltrim":0,"rtrim":0,"nsites":2945,"evalue":"2.7e-1053","pwm":[[9.41224e-06,7.56511e-06,0.999974,9.41224e-06],[9.41224e-06,7.56511e-06,0.999974,9.41224e-06],[9.41224e-06,7.56511e-06,0.999974,9.41224e-06],[0.378265,0.3657,7.56511e-06,0.256028],[9.41224e-06,0.999974,7.56511e-06,9.41224e-06],[9.41224e-06,0.999974,7.56511e-06,9.41224e-06],[0.555169,0.444814,7.56511e-06,9.41224e-06]]}],"sequence_names":[],"primaries":[{"motif":{"db":0,"id":"CCCACCCCGAATTTC","alt":"MEME-2","len":15,"ltrim":0,"rtrim":0,"nsites":869,"evalue":"6.7e-1223","pwm":[[0.0103877,0.90556,0.0690627,0.0149902],[3.18951e-05,0.963091,0.00232637,0.0345499],[3.18951e-05,0.883698,2.56357e-05,0.116245],[0.973453,2.56357e-05,0.0264896,3.18951e-05],[0.0069351,0.933175,2.56357e-05,0.059864],[0.00233263,0.974597,2.56357e-05,0.0230442],[0.00233263,0.948133,2.56357e-05,0.0495082],[0.00923684,0.909012,2.56357e-05,0.0817255],[0.188733,0.00232637,0.808909,3.18951e-05],[0.87565,2.56357e-05,0.124292,3.18951e-05],[0.996465,2.56357e-05,2.56357e-05,0.0034835],[0.0069351,0.0656111,2.56357e-05,0.927428],[3.18951e-05,2.56357e-05,2.56357e-05,0.999917],[3.18951e-05,2.56357e-05,2.56357e-05,0.999917],[3.18951e-05,0.929723,2.56357e-05,0.0702198]]},"secondaries":[[{"idx":0,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":1,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":2,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":3,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":4,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":5,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":6,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":7,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":8,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}],[{"idx":9,"counts":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],"spacings":[{"orient":0,"bin":0,"pvalue":1,"inferred_pwm":[[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25],[0.25,0.25,0.25,0.25]],"alignment_pwm":[null,null,null,null],"seqs":[]}]}]]}],"run_time":{"cpu":1.99,"real":2}};
    </script>
    <script type="text/javascript">
var site_url = "http://meme-suite.org";

    </script>
    <script type="text/javascript">
/*
 * $
 *
 * Shorthand function for getElementById
 */
function $(el) {
  return document.getElementById(el);
}


/*
 * See http://stackoverflow.com/a/5450113/66387
 * Does string multiplication like the perl x operator.
 */
function string_mult(pattern, count) {
    if (count < 1) return '';
    var result = '';
    while (count > 1) {
        if (count & 1) result += pattern;
        count >>= 1, pattern += pattern;
    }
    return result + pattern;
}

/*
 * See http://stackoverflow.com/questions/814613/how-to-read-get-data-from-a-url-using-javascript
 * Slightly modified with information from
 * https://developer.mozilla.org/en/DOM/window.location
 */
function parse_params() {
  "use strict";
  var search, queryStart, queryEnd, query, params, nvPairs, i, nv, n, v;
  search = window.location.search;
  queryStart = search.indexOf("?") + 1;
  queryEnd   = search.indexOf("#") + 1 || search.length + 1;
  query      = search.slice(queryStart, queryEnd - 1);

  if (query === search || query === "") return {};

  params  = {};
  nvPairs = query.replace(/\+/g, " ").split("&");

  for (i = 0; i < nvPairs.length; i++) {
    nv = nvPairs[i].split("=");
    n  = decodeURIComponent(nv[0]);
    v  = decodeURIComponent(nv[1]);
    // allow a name to be used multiple times
    // storing each value in the array
    if (!(n in params)) {
      params[n] = [];
    }
    params[n].push(nv.length === 2 ? v : null);
  }
  return params;
}

/*
 * coords
 *
 * Calculates the x and y offset of an element.
 * From http://www.quirksmode.org/js/findpos.html
 * with alterations to take into account scrolling regions
 */
function coords(elem) {
  var myX = myY = 0;
  if (elem.getBoundingClientRect) {
    var rect;
    rect = elem.getBoundingClientRect();
    myX = rect.left + ((typeof window.pageXOffset !== "undefined") ?
        window.pageXOffset : document.body.scrollLeft);
    myY = rect.top + ((typeof window.pageYOffset !== "undefined") ?
        window.pageYOffset : document.body.scrollTop);
  } else {
    // this fall back doesn't properly handle absolutely positioned elements
    // inside a scrollable box
    var node;
    if (elem.offsetParent) {
      // subtract all scrolling
      node = elem;
      do {
        myX -= node.scrollLeft ? node.scrollLeft : 0;
        myY -= node.scrollTop ? node.scrollTop : 0;
      } while (node = node.parentNode);
      // this will include the page scrolling (which is unwanted) so add it back on
      myX += (typeof window.pageXOffset !== "undefined") ? window.pageXOffset : document.body.scrollLeft;
      myY += (typeof window.pageYOffset !== "undefined") ? window.pageYOffset : document.body.scrollTop;
      // sum up offsets
      node = elem;
      do {
        myX += node.offsetLeft;
        myY += node.offsetTop;
      } while (node = node.offsetParent);
    }
  }
  return [myX, myY];
}

/*
 * position_popup
 *
 * Positions a popup relative to an anchor element.
 *
 * The available positions are:
 * 0 - Centered below the anchor.
 */
function position_popup(anchor, popup, position) {
  "use strict";
  var a_x, a_y, a_w, a_h, p_x, p_y, p_w, p_h;
  var a_xy, spacer, margin, scrollbar, page_w;
  // define constants
  spacer = 5;
  margin = 15;
  scrollbar = 15;
  // define the positions and widths
  a_xy = coords(anchor);
  a_x = a_xy[0];
  a_y = a_xy[1];
  a_w = anchor.offsetWidth;
  a_h = anchor.offsetHeight;
  p_w = popup.offsetWidth;
  p_h = popup.offsetHeight;
  page_w = null;
  if (window.innerWidth) {
    page_w = window.innerWidth;
  } else if (document.body) {
    page_w = document.body.clientWidth;
  }
  // check the position type is defined
  if (typeof position !== "number") {
    position = 0;
  }
  // calculate the popup position
  switch (position) {
    case 1:
      p_x = a_x + a_w + spacer;
      p_y = a_y + (a_h / 2) - (p_h / 2);
      break;
    case 0:
    default:
      p_x = a_x + (a_w / 2) - (p_w / 2);
      p_y = a_y + a_h + spacer;
      break;
  }
  // constrain the popup position
  if (p_x < margin) {
    p_x = margin;
  } else if (page_w != null && (p_x + p_w) > (page_w - margin - scrollbar)) {
    p_x = page_w - margin - scrollbar - p_w;
  }
  if (p_y < margin) {
    p_y = margin;
  }
  // position the popup
  popup.style.left = p_x + "px";
  popup.style.top = p_y + "px";
}

function lookup_help_popup(popup_id) {
  var _body, pop, info;
  pop = document.getElementById(popup_id);
  if (pop == null) {
    _body = document.getElementsByTagName("body")[0];
    pop = document.createElement("div");
    pop.className = "pop_content";
    pop.id = popup_id;
    pop.style.backgroundColor = "#FFC";
    pop.style.borderColor = "black";
    info = document.createElement("p");
    info.style.fontWeight = "bold";
    info.appendChild(document.createTextNode("Error: No popup for topic \"" + popup_id + "\"."));
    pop.appendChild(info);
    // this might cause problems with the menu, but as this only happens
    // when something is already wrong I don't think that's too much of a problem
    _body.insertBefore(pop, _body.firstChild);
  }
  if (document.getElementsByTagName('body')[0].hasAttribute("data-autobtns")) {
    if (!/\bauto_buttons\b/.test(pop.className)) {
      pop.className += " auto_buttons";
      var back_btn_sec = document.createElement("div");
      back_btn_sec.className = "nested_only pop_back_sec";
      var back_btn = document.createElement("span");
      back_btn.className = "pop_back";
      back_btn.appendChild(document.createTextNode("<< back"));
      back_btn.addEventListener("click", function(e) {
        help_return();
      }, false);
      back_btn_sec.appendChild(back_btn);
      pop.insertBefore(back_btn_sec, pop.firstChild);
      var close_btn_sec = document.createElement("div");
      close_btn_sec.className = "pop_close_sec";
      var close_btn = document.createElement("span");
      close_btn.className = "pop_close";
      close_btn.appendChild(document.createTextNode("close"));
      close_btn.addEventListener("click", function(e) {
        help_popup();
      }, false);
      close_btn_sec.appendChild(close_btn);
      pop.appendChild(close_btn_sec);
    }
  }
  return pop;
}

/*
 * help_popup
 *
 * Moves around help pop-ups so they appear
 * below an activator.
 */
function help_popup(activator, popup_id) {
  "use strict";
  var pop;
  // set default values
  if (typeof help_popup.popup === "undefined") {
    help_popup.popup = [];
  }
  if (typeof help_popup.activator === "undefined") {
    help_popup.activator = null;
  }
  var last_pop = (help_popup.popup.length > 0 ? help_popup.popup[help_popup.popup.length - 1] : null);
  if (typeof(activator) == "undefined") { // no activator so hide
    if (last_pop != null) {
      last_pop.style.display = 'none';
      help_popup.popup = [];
    }
    return;
  }
  pop = lookup_help_popup(popup_id);
  if (pop == last_pop) {
    if (activator == help_popup.activator) {
      //hide popup (as we've already shown it for the current help button)
      last_pop.style.display = 'none';
      help_popup.popup = [];
      return; // toggling complete!
    }
  } else if (last_pop != null) {
    //activating different popup so hide current one
    last_pop.style.display = 'none';
  }
  help_popup.popup = [pop];
  help_popup.activator = activator;
  toggle_class(pop, "nested", false);
  //must make the popup visible to measure it or it has zero width
  pop.style.display = 'block';
  position_popup(activator, pop);
}

/*
 * help_refine
 * 
 * Intended for links within a help popup. Stores a stack of state so
 * you can go back.
 */
function help_refine(popup_id) {
  if (help_popup.popup == null || help_popup.popup.length == 0 || help_popup.activator == null) {
    //throw new Error("Cannot refine a help popup when one is not shown!");
    var pop = lookup_help_popup(popup_id);
    var act_id = popup_id + '_act';
    var activator = document.getElementById(act_id);
    help_popup(activator, popup_id);
  }
  var pop = lookup_help_popup(popup_id);
  var last_pop = help_popup.popup[help_popup.popup.length - 1];
  if (pop == last_pop) return; // slightly odd, but no real cause for alarm
  help_popup.popup.push(pop);
  toggle_class(pop, "nested", true);
  last_pop.style.display = "none";
  //must make the popup visible to measure it or it has zero width
  pop.style.display = "block";
  position_popup(help_popup.activator, pop);
}

/*
 * help_return
 * 
 * Intended for links within a help popup. Stores a stack of state so
 * you can go back.
 */
function help_return() {
  if (help_popup.popup == null || help_popup.popup.length == 0 || help_popup.activator == null) {
    throw new Error("Can not return to a earlier help popup when one is not shown!");
  }
  var last_pop = help_popup.popup.pop();
  last_pop.style.display = "none";
  var pop = (help_popup.popup.length > 0 ? help_popup.popup[help_popup.popup.length - 1] : null);
  if (pop != null) {
    toggle_class(pop, "nested", help_popup.popup.length > 1);
    pop.style.display = "block";
    position_popup(help_popup.activator, pop);
  } else {
    help_popup.activator = null;
  }
}

/*
 * update_scroll_pad
 *
 * Creates padding at the bottom of the page to allow
 * scrolling of anything into view.
 */
function update_scroll_pad() {
  var page, pad;
  page = (document.compatMode === "CSS1Compat") ? document.documentElement : document.body;
  pad = $("scrollpad");
  if (pad === null) {
    pad = document.createElement("div");
    pad.id = 'scrollpad';
    document.getElementsByTagName('body')[0].appendChild(pad);
  }
  pad.style.height = Math.abs(page.clientHeight - 100) + "px";
}

function substitute_classes(node, remove, add) {
  "use strict";
  var list, all, i, cls, classes;
  list = node.className.split(/\s+/);
  all = {};
  for (i = 0; i < list.length; i++) {
    if (list[i].length > 0) all[list[i]] = true;
  }
  for (i = 0; i < remove.length; i++) {
    if (all.hasOwnProperty(remove[i])) {
      delete all[remove[i]];
    }
  }
  for (i = 0; i < add.length; i++) {
    all[add[i]] = true;
  }
  classes = "";
  for (cls in all) {
    classes += cls + " ";
  }
  node.className = classes;
}

/*
 * toggle_class
 *
 * Adds or removes a class from the node. If the parameter 'enabled' is not 
 * passed then the existence of the class will be toggled, otherwise it will be
 * included if enabled is true.
 */
function toggle_class(node, cls, enabled) {
  var classes = node.className;
  var list = classes.replace(/^\s+/, '').replace(/\s+$/, '').split(/\s+/);
  var found = false;
  for (var i = 0; i < list.length; i++) {
    if (list[i] == cls) {
      list.splice(i, 1);
      i--;
      found = true;
    }
  }
  if (typeof enabled == "undefined") {
    if (!found) list.push(cls);
  } else {
    if (enabled) list.push(cls);
  }
  node.className = list.join(" ");
}

/*
 * find_child
 *
 * Searches child nodes in depth first order and returns the first it finds
 * with the className specified.
 * TODO replace with querySelector
 */
function find_child(node, className) {
  var pattern;
  if (node == null || typeof node !== "object") {
    return null;
  }
  if (typeof className === "string") {
    pattern = new RegExp("\\b" + className + "\\b");
  } else {
    pattern = className;
  }
  if (node.nodeType == Node.ELEMENT_NODE && 
      pattern.test(node.className)) {
    return node;
  } else {
    var result = null;
    for (var i = 0; i < node.childNodes.length; i++) {
      result = find_child(node.childNodes[i], pattern);
      if (result != null) break;
    }
    return result;
  }
}

/*
 * find_parent
 *
 * Searches parent nodes outwards from the node and returns the first it finds
 * with the className specified.
 */
function find_parent(node, className) {
  var pattern;
  pattern = new RegExp("\\b" + className + "\\b");
  do {
    if (node.nodeType == Node.ELEMENT_NODE && 
        pattern.test(node.className)) {
      return node;
    }
  } while (node = node.parentNode);
  return null;
}

/*
 * find_parent_tag
 *
 * Searches parent nodes outwards from the node and returns the first it finds
 * with the tag name specified. HTML tags should be specified in upper case.
 */
function find_parent_tag(node, tag_name) {
  do {
    if (node.nodeType == Node.ELEMENT_NODE && node.tagName == tag_name) {
      return node;
    }
  } while (node = node.parentNode);
  return null;
}

/*
 * __toggle_help
 *
 * Uses the 'topic' property of the this object to
 * toggle display of a help topic.
 *
 * This function is not intended to be called directly.
 */
function __toggle_help(e) {
  if (!e) e = window.event;
  if (e.type === "keydown") {
    if (e.keyCode !== 13 && e.keyCode !== 32) {
      return;
    }
    // stop a submit or something like that
    e.preventDefault();
  }

  help_popup(this, this.getAttribute("data-topic"));
}

function setup_help_button(button) {
  "use strict";
  var topic;
  if (button.hasAttribute("data-topic")) {
    topic = button.getAttribute("data-topic");
    if (document.getElementById(topic) != null) {
      button.tabIndex = "0"; // make keyboard selectable
      button.addEventListener("click", function() {
        help_popup(button, topic);
      }, false);
      button.addEventListener("keydown", function(e) {
        // toggle only on Enter or Spacebar, let other keys do their thing
        if (e.keyCode !== 13 && e.keyCode !== 32) return;
        // stop a submit or something like that
        e.preventDefault();
        help_popup(button, topic);
      }, false);
    } else {
      button.style.visibility = "hidden";
    }
  }
  button.className += " active";
}

/*
 * help_button
 *
 * Makes a help button for the passed topic.
 */
function help_button(topic) {
  var btn = document.createElement("div");
  btn.className = "help";
  btn.setAttribute("data-topic", topic);
  setup_help_button(btn);
  return btn;
}

/*
 * prepare_download
 *
 * Sets the attributes of a link to setup a file download using the given content.
 * If no link is provided then create one and click it.
 */
function prepare_download(content, mimetype, filename, link) {
  "use strict";
  // if no link is provided then create one and click it
  var click_link = false;
  if (!link) {
    link = document.createElement("a");
    click_link = true;
  }
  try {
    // Use a BLOB to convert the text into a data URL.
    // We could do this manually with a base 64 conversion.
    // This will only be supported on modern browsers,
    // hence the try block.
    var blob = new Blob([content], {type: mimetype});
    var reader = new FileReader();
    reader.onloadend = function() {
      // If we're lucky the browser will also support the download
      // attribute which will let us suggest a file name to save the link.
      // Otherwise it is likely that the filename will be unintelligible. 
      link.setAttribute("download", filename);
      link.href = reader.result;
      if (click_link) {
        // must add the link to click it
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      }
    }
    reader.readAsDataURL(blob);
  } catch (error) {
    if (console && console.log) console.log(error);
    // probably an old browser
    link.href = "";
    link.visible = false;
  }
}

/*
 * add_cell
 *
 * Add a cell to the table row.
 */
function add_cell(row, node, cls, click_action) {
  var cell = row.insertCell(row.cells.length);
  if (node) cell.appendChild(node);
  if (cls && cls !== "") cell.className = cls;
  if (click_action) cell.addEventListener("click", click_action, false);
}

/*
 * add_header_cell
 *
 * Add a header cell to the table row.
 */
function add_header_cell(row, node, help_topic, cls, colspan) {
  var th = document.createElement("th");
  if (node) th.appendChild(node);
  if (help_topic && help_topic !== "") th.appendChild(help_button(help_topic));
  if (cls && cls !== "") th.className = cls;
  if (typeof colspan == "number" && colspan > 1) th.colSpan = colspan;
  row.appendChild(th);
}

/*
 * add_text_cell
 *
 * Add a text cell to the table row.
 */
function add_text_cell(row, text, cls, action) {
  var node = null;
  if (typeof(text) != 'undefined') node = document.createTextNode(text);
  add_cell(row, node, cls, action);
}

/*
 * add_text_header_cell
 *
 * Add a text header cell to the table row.
 */
function add_text_header_cell(row, text, help_topic, cls, action, colspan) {
  var node = null;
  if (typeof(text) != 'undefined') {
    var nbsp = (help_topic ? "\u00A0" : "");
    var str = "" + text;
    var parts = str.split(/\n/);
    if (parts.length === 1) {
      if (action) {
        node = document.createElement("span");
        node.appendChild(document.createTextNode(str + nbsp));
      } else {
        node = document.createTextNode(str + nbsp);
      }
    } else {
      node = document.createElement("span");
      for (var i = 0; i < parts.length; i++) {
        if (i !== 0) {
          node.appendChild(document.createElement("br"));
        }
        node.appendChild(document.createTextNode(parts[i]));
      }
    }
    if (action) {
      node.addEventListener("click", action, false);
      node.style.cursor = "pointer";
    }
  }
  add_header_cell(row, node, help_topic, cls, colspan);
}

function setup_help() {
  "use strict";
  var help_buttons, i;
  help_buttons = document.querySelectorAll(".help:not(.active)");
  for (i = 0; i < help_buttons.length; i++) {
    setup_help_button(help_buttons[i]);
  }
}

function setup_scrollpad() {
  "use strict";
  if (document.getElementsByTagName('body')[0].hasAttribute("data-scrollpad") && document.getElementById("scrollpad") == null) {
    window.addEventListener("resize", update_scroll_pad, false);
    update_scroll_pad();
  }
}

// anon function to avoid polluting global scope
(function() {
  "use strict";
  window.addEventListener("load", function load(evt) {
    window.removeEventListener("load", load, false);
    setup_help();
    setup_scrollpad();
  }, false);
})();

/*
 *  make_link
 *
 *  Creates a text node and if a URL is specified it surrounds it with a link.
 *  If the URL doesn't begin with "http://" it automatically adds it, as
 *  relative links don't make much sense in this context.
 */
function make_link(text, url) {
  var textNode = null;
  var link = null;
  if (typeof text !== "undefined" && text !== null) textNode = document.createTextNode(text);
  if (typeof url === "string") {
    if (url.indexOf("//") == -1) {
      url = "http://" + url;
    }
    link = document.createElement('a');
    link.href = url;
    if (textNode) link.appendChild(textNode);
    return link;
  }
  return textNode;
}

//
// Function to create an HTML paragraph describing the 
// MEME Suite background model source.
//
function make_background_source(title, source, text) {
  var paraNode = document.createElement("P");
  var titleNode = document.createElement("B");
  var textNode1 = document.createTextNode("\u00A0\u00A0\u00A0\u00A0" + title + ": ");
  titleNode.appendChild(textNode1);
  var source_text = ((source == "--motif--") ? "the (first) motif file" : (source == "--nrdb--") ? "an old version of the NCBI non-redundant database" : (source == "--uniform--") ? "the uniform model" : (source == "--query--") ? "the query file" : (source == "--sequences--") ? "built from the (primary) sequences" : ("the file '" + source + "'"));
  if (text) { return source_text; }
  var textNode2 = document.createTextNode(source_text);
  paraNode.appendChild(titleNode);
  paraNode.appendChild(textNode2);
  return paraNode;
}

// Function to create a help button
function make_help_button(container, help_topic) {
  container.appendChild(help_button(help_topic));
}

    </script>
    <script type="text/javascript">
function rad2deg(rad) {
  return rad * 180 / Math.PI;
}

function parse_line_join(line_join) {
  line_join = line_join.toLowerCase();
  if (line_join == "bevel") {
    return 2;
  } else if (line_join == "round") {
    return 1;
  } else { // miter
    return 0;
  }
}

function parse_line_cap(line_cap) {
  line_cap = line_cap.toLowerCase();
  if (line_cap == "square") {
    return 2;
  } else if (line_cap == "round") {
    return 1;
  } else { // butt 
    return 0;
  }
}

function parse_text_align(text_align) {
  text_align = text_align.toLowerCase();
  if (text_align == "center") {
    return "center";
  } else if (text_align == "end" || text_align == "right") {
    return "right"; 
  } else { // start or left
    return "left";
  }
}

function parse_text_baseline(text_baseline) {
  text_baseline = text_baseline.toLowerCase();
  if (text_baseline == "top" || text_baseline == "hanging") {
    return "top";
  } else if (text_baseline == "middle") {
    return "middle";
  } else if (text_baseline == "bottom") {
    return "bottom";
  } else { // alphabetic or ideographic
    return "alphabetic";
  }
}

function parse_colour(colour) {
  var hex6_re = /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$/;
  var hex3_re = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/;
  var rgb_re = /^\s*rgb\s*\(\s*(\d{1,3})(%?)\s*,\s*(\d{1,3})(%?)\s*,\s*(\d{1,3})(%?)\s*\)\s*$/;
  var rgba_re = /^\s*rgba\s*\(\s*(\d{1,3})(%?)\s*,\s*(\d{1,3})(%?)\s*,\s*(\d{1,3})(%?)\s*,\s*(\d{1,3})(%?)\s*\)\s*$/;
  if (colour == null || colour == "") {
    return {'red': 0, 'green': 0, 'blue': 0, 'alpha': 255};
  }
  colour = colour.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); //trim
  colour = colour.toLowerCase();
  if (colour == "maroon") {// #800000
    return {'red': 128, 'green': 0, 'blue': 0, 'alpha': 255};
  } else if (colour == "red") { // #FF0000
    return {'red': 255, 'green': 0, 'blue': 0, 'alpha': 255};
  } else if (colour == "orange") { // FFA500
    return {'red': 255, 'green': 165, 'blue': 0, 'alpha': 255};
  } else if (colour == "yellow") { // #FFFF00
    return {'red': 255, 'green': 255, 'blue': 0, 'alpha': 255};
  } else if (colour == "olive") { // #808000
    return {'red': 128, 'green': 128, 'blue': 0, 'alpha': 255};
  } else if (colour == "purple") { // #800080
    return {'red': 128, 'green': 0, 'blue': 128, 'alpha': 255};
  } else if (colour == "fuchsia" || colour == "magenta") { // #FF00FF
    return {'red': 255, 'green': 0, 'blue': 255, 'alpha': 255};
  } else if (colour == "white") { // #FFFFFF
    return {'red': 255, 'green': 255, 'blue': 255, 'alpha': 255};
  } else if (colour == "lime") { // #00FF00
    return {'red': 0, 'green': 255, 'blue': 0, 'alpha': 255};
  } else if (colour == "green") { // #008000
    return {'red': 0, 'green': 128, 'blue': 0, 'alpha': 255};
  } else if (colour == "navy") { // #000080
    return {'red': 0, 'green': 0, 'blue': 128, 'alpha': 255};
  } else if (colour == "blue") { // #0000FF
    return {'red': 0, 'green': 0, 'blue': 255, 'alpha': 255};
  } else if (colour == "aqua" || colour == "cyan") { // #00FFFF
    return {'red': 0, 'green': 255, 'blue': 255, 'alpha': 255};
  } else if (colour == "teal") { // #008080
    return {'red': 0, 'green': 128, 'blue': 128, 'alpha': 255};
  } else if (colour == "black") { // #000000
    return {'red': 0, 'green': 0, 'blue': 0, 'alpha': 255};
  } else if (colour == "silver") { // #C0C0C0
    return {'red': 192, 'green': 192, 'blue': 192, 'alpha': 255};
  } else if (colour == "gray") { // #808080
    return {'red': 128, 'green': 128, 'blue': 128, 'alpha': 255};
  }
  var matches;
  matches = hex6_re.exec(colour);
  if (matches) {
    var red = parseInt(matches[1], 16);
    var green = parseInt(matches[2], 16);
    var blue = parseInt(matches[3], 16);
    return {'red': red, 'green': green, 'blue': blue, 'alpha': 255};
  }
  matches = hex3_re.exec(colour);
  if (matches) {
    var red = parseInt(matches[1] + matches[1], 16);
    var green = parseInt(matches[2] + matches[2], 16);
    var blue = parseInt(matches[3] + matches[3], 16);
    return {'red': red, 'green': green, 'blue': blue, 'alpha': 255};
  }
  matches = rgb_re.exec(colour);
  if (matches) {
    var red = parseInt(matches[1]);
    if (matches[2] == "%") red = Math.round((red * 255) / 100);
    var green = parseInt(matches[3]);
    if (matches[4] == "%") green = Math.round((green * 255) / 100);
    var blue = parseInt(matches[5]);
    if (matches[6] == "%") blue = Math.round((blue * 255) / 100);
    return {'red': red, 'green': green, 'blue': blue, 'alpha': 255};
  }
  matches = rgba_re.exec(colour);
  if (matches) {
    var red = parseInt(matches[1]);
    if (matches[2] == "%") red = Math.round((red * 255) / 100);
    var green = parseInt(matches[3]);
    if (matches[4] == "%") green = Math.round((green * 255) / 100);
    var blue = parseInt(matches[5]);
    if (matches[6] == "%") blue = Math.round((blue * 255) / 100);
    var alpha = parseInt(matches[7]);
    if (matches[8] == "%") alpha = Math.round((alpha * 255) / 100);
    return {'red': red, 'green': green, 'blue': blue, 'alpha': alpha};
  }
  // default to black
  throw new Error("Failed to parse colour: " + colour);
}

function colour_equals(colour1, colour2) {
  if (colour1.red != colour2.red) return false;
  if (colour1.green != colour2.green) return false;
  if (colour1.blue != colour2.blue) return false;
  if (colour1.alpha != colour2.alpha) return false;
  return true;
}

// splits a font string into words
function split_words(str) {
  var words = [];
  var start = -1;
  var space = /\s/;
  var single_quote = false;
  var double_quote = false;
  // read words
  for (var i = 0; i < str.length; i++) {
    if (start == -1) {
      if (!space.test(str.charAt(i))) {
        switch (str.charAt(i)) {
          case "'":
            single_quote = true;
            start = i + 1;
            break;
          case '"':
            double_quote = true;
            start = i + 1;
            break;
          default:
            start = i;
        }
      }
    } else {
      if (!single_quote && !double_quote) {
        if (space.test(str.charAt(i))) {
          var len = i - start;
          if (len > 0) words.push(str.substr(start, len));
          start = -1;
        } else if (str.charAt(i) == "'" || str.charAt(i) == '"') {
          throw new Error("Quote in the middle of an unquoted word!");
        }
      } else if (single_quote) {
        if (str.charAt(i) == "'") {
          var len = i - start;
          if (len > 0) words.push(str.substr(start, len));
          start = -1;
          single_quote = false;
        }
      } else if (double_quote) {
        if (str.charAt(i) == '"') {
          var len = i - start;
          if (len > 0) words.push(str.substr(start, len));
          start = -1;
          double_quote = false;
        }
      }
    }
  }
  if (start != -1) {
    if (single_quote || double_quote) throw new Error("Unterminated quoted region");
    words.push(str.substr(start));
  }
  return words;
}

function add_intercepts(ctx2d, eps_callback) {
  ctx2d.eps_callback = eps_callback;
  ctx2d.save = function() {
    this.eps_callback.save();
    Object.getPrototypeOf(this).save.call(this);
  };
  ctx2d.restore = function() {
    this.eps_callback.restore();
    Object.getPrototypeOf(this).restore.call(this);
  };
  ctx2d.beginPath = function() {
    this.eps_callback.beginPath();
    Object.getPrototypeOf(this).beginPath.call(this);
  };
  ctx2d.closePath = function() {
    this.eps_callback.closePath();
    Object.getPrototypeOf(this).closePath.call(this);
  };
  ctx2d.moveTo = function(x, y) {
    this.eps_callback.moveTo(x, y);
    Object.getPrototypeOf(this).moveTo.call(this, x, y);
  };
  ctx2d.lineTo = function(x, y) {
    this.eps_callback.lineTo(x, y);
    Object.getPrototypeOf(this).lineTo.call(this, x, y);
  };
  ctx2d.rect = function(x, y, w, h) {
    this.eps_callback.rect(x, y, w, h);
    Object.getPrototypeOf(this).rect.call(this, x, y, w, h);
  };
  ctx2d.arc = function(x, y, radius, startAngle, endAngle, anticlockwise) {
    this.eps_callback.arc(x, y, radius, startAngle, endAngle, anticlockwise);
    Object.getPrototypeOf(this).arc.call(this, x, y, radius, startAngle, endAngle, anticlockwise);
  };
  ctx2d.arcTo = function(cpx1, cpy1, cpx2, cpy2, radius) {
    this.eps_callback.arcTo(cpx1, cpy1, cpx2, cpy2, radius);
    Object.getPrototypeOf(this).arcTo.call(this, cpx1, cpy1, cpx2, cpy2, radius);
  };
  ctx2d.quadraticArcTo = function(cpx, cpy, x, y) {
    this.eps_callback.quadraticArcTo(cpx, cpy, x, y);
    Object.getPrototypeOf(this).quadraticArcTo.call(this, cpx, cpy, x, y);
  };
  ctx2d.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
    this.eps_callback.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
    Object.getPrototypeOf(this).bezierCurveTo.call(this, cp1x, cp1y, cp2x, cp2y, x, y);
  };
  ctx2d.stroke = function() {
    this.eps_callback.stroke();
    Object.getPrototypeOf(this).stroke.call(this);
  };
  ctx2d.fill = function() {
    this.eps_callback.fill();
    Object.getPrototypeOf(this).fill.call(this);
  };
  ctx2d.clip = function() {
    this.eps_callback.clip();
    Object.getPrototypeOf(this).clip.call(this);
  };
  ctx2d.fillRect = function(x, y, width, height) {
    this.eps_callback.fillRect(x, y, width, height);
    Object.getPrototypeOf(this).fillRect.call(this, x, y, width, height);
  };
  ctx2d.strokeRect = function(x, y, width, height) {
    this.eps_callback.strokeRect(x, y, width, height);
    Object.getPrototypeOf(this).strokeRect.call(this, x, y, width, height);
  };
  ctx2d.clearRect = function(x, y, width, height) {
    this.eps_callback.clearRect(x, y, width, height);
    Object.getPrototypeOf(this).clearRect.call(this, x, y, width, height);
  };
  ctx2d.fillText = function(string, x, y) {
    this.eps_callback.fillText(string, x, y);
    Object.getPrototypeOf(this).fillText.call(this, string, x, y);
  };
  ctx2d.translate = function(dx, dy) {
    this.eps_callback.translate(dx, dy);
    Object.getPrototypeOf(this).translate.call(this, dx, dy);
  };
  ctx2d.rotate = function(angle) {
    this.eps_callback.rotate(angle);
    Object.getPrototypeOf(this).rotate.call(this, angle);
  };
  ctx2d.scale = function(sx, sy) {
    this.eps_callback.scale(sx, sy);
    Object.getPrototypeOf(this).scale.call(this, sx, sy);
  };
  ctx2d.transform = function(m11, m12, m21, m22, dx, dy) {
    this.eps_callback.transform(m11, m12, m21, m22, dx, dy);
    Object.getPrototypeOf(this).transform.call(this, m11, m12, m21, m22, dx, dy);
  };
  ctx2d.setTransform = function(m11, m12, m21, m22, dx, dy) {
    this.eps_callback.setTransform(m11, m12, m21, m22, dx, dy);
    Object.getPrototypeOf(this).setTransform.call(this, m11, m12, m21, m22, dx, dy);
  };
}

var EpsState = function(copy) {
  // canvas vars
  this.activeStyle = {'red': 0, 'green': 0, 'blue': 0, 'alpha': 255};
  this.fillStyle = {'red': 0, 'green': 0, 'blue': 0, 'alpha': 255};
  this.strokeStyle = {'red': 0, 'green': 0, 'blue': 0, 'alpha': 255};
  this.lineWidth = 1.0;
  this.lineCap = 0;
  this.lineJoin = 0;
  this.miterLimit = 10.0;
  this.font = null;
  this.textAlign = "left";
  this.textBaseline = "alphabetic";
  if (copy) {
    this.activeStyle = copy.activeStyle;
    this.fillStyle = copy.fillStyle;
    this.strokeStyle = copy.strokeStyle;
    this.lineWidth = copy.lineWidth;
    this.lineCap = copy.lineCap;
    this.lineJoin = copy.lineJoin;
    this.miterLimit = copy.miterLimit;
    this.font = copy.font;
    this.textAlign = copy.textAlign;
    this.textBaseline = copy.textBaseline;
  }
};


var EpsContext = function(ctx, width, height) {
  var title = "Image Title";
  var creator = "Image Creator";
  var date = new Date();
  // private parameters
  this.ctx = ctx;
  this.width = width;
  this.height = height;
  this.stack = [];
  this.current_state = new EpsState();
  this.font_lookup = {};
  this.indent = "";
  this.eps_text = 
    "%!PS-Adobe-3.0 EPSF-3.0\n" + 
    "%%Title: " + title + "\n" + 
    "%%Creator: " + creator + "\n" +
    "%%CreationDate: " + date.toUTCString() + "\n" + 
    "%%BoundingBox: 0 0 " + (width * 0.75) + " " + (height * 0.75) + "\n" +
    "%%Pages: 0\n" +
    "%%DocumentFonts:\n" +
    "%%EndComments\n" +
    "0.75 0.75 scale\n" +
    "0 " + height + " translate\n" + 
    "1 -1 scale\n";
  add_intercepts(ctx, this);
};

// look for differences between the current state and the settings to see 
// what has changed. Apply the changes to the eps file.
EpsContext.prototype.detect = function() {
  var state = this.current_state;
  var ctx = this.ctx;
  if (ctx.lineWidth != state.lineWidth) {
    state.lineWidth = ctx.lineWidth;
    this.eps_text += this.indent + state.lineWidth + " setlinewidth\n";
  }
  var cap = parse_line_cap(ctx.lineCap);
  if (cap != state.lineCap) {
    state.lineCap = cap;
    this.eps_text += this.indent + state.lineCap + " setlinecap\n";
  }
  var join = parse_line_join(ctx.lineJoin);
  if (join != state.lineJoin) {
    state.lineJoin = join;
    this.eps_text += this.indent + state.lineJoin + " setlinejoin\n";
  }
  var miterLimit = parseInt(ctx.miterLimit);
  if (miterLimit != state.miterLimit) {
    state.miterLimit = miterLimit;
    this.eps_text += this.indent + state.miterLimit + " setmiterlimit\n";
  }
  var strokeStyle = parse_colour(ctx.strokeStyle);
  if (!colour_equals(strokeStyle, state.strokeStyle)) {
    state.strokeStyle = strokeStyle;
    this.activateStyle(false);
  }
  var fillStyle = parse_colour(ctx.fillStyle);
  if (!colour_equals(fillStyle, state.fillStyle)) {
    state.fillStyle = fillStyle;
    this.activateStyle(true);
  }
  var textAlign = parse_text_align(ctx.textAlign);
  if (textAlign != state.textAlign) {
    state.textAlign = textAlign;
  }
  var textBaseline = parse_text_baseline(ctx.textBaseline);
  if (textBaseline != state.textBaseline) {
    state.textBaseline = textBaseline;
  }
  var font = this.lookup_font(ctx.font);
  if (font != null && font !== state.font) {
    state.font = font;
    this.eps_text += this.indent + "/" + font.name + " findfont " +  
      font.size + " scalefont setfont\n";
  }
};

EpsContext.prototype.activateStyle = function(useFillStyle) {
  var state = this.current_state;
  var style = (useFillStyle ? state.fillStyle : state.strokeStyle);
  if (!colour_equals(state.activeStyle, style)) {
    this.eps_text += this.indent + (style.red / 255) + " " + 
      (style.green / 255) + " " + (style.blue / 255) + " setrgbcolor\n";
    state.activeStyle = style;
  }
};


// saves the current state on the stack
EpsContext.prototype.save = function() {
  this.detect();
  // gsave
  this.eps_text += this.indent + "gsave\n";
  this.stack.push(new EpsState(this.current_state));
  this.indent += "  ";
};

// restores the last saved state
EpsContext.prototype.restore = function() {
  if (this.stack.length == 0) throw new Error("Call to restore not matched with call to save.");
  this.detect();
  this.current_state = this.stack.pop();
  this.indent = Array(this.stack.length + 1).join("  ");
  // grestore
  this.eps_text += this.indent + "grestore\n";
};

// start a path
EpsContext.prototype.beginPath = function() {
  this.detect();
  // newpath
  this.eps_text += this.indent + "newpath\n";
};

// join the current position to the path start
EpsContext.prototype.closePath = function() {
  this.detect();
  // closepath
  this.eps_text += this.indent + "closepath\n";
};

// move the current position
EpsContext.prototype.moveTo = function(x, y) {
  this.detect();
  // moveto
  this.eps_text += this.indent + x + " " + y + " moveto\n";
};

// join the current position to a new position and update the current position
EpsContext.prototype.lineTo = function(x, y) {
  this.detect();
  // lineto
  this.eps_text += this.indent + x + " " + y + " lineto\n";
};

// add a rectangle to the path
EpsContext.prototype.rect = function(x, y, width, height) {
  this.moveTo(x, y);
  this.lineTo(x + width, y);
  this.lineTo(x + width, y + height);
  this.lineTo(x, y + height);
  this.closePath();
};

// add an arc to the path
EpsContext.prototype.arc = function(x, y, radius, startAngle, endAngle, anticlockwise) {
  this.detect();
  if (anticlockwise) {
    // command "X Y RADIUS START_ANGLE END_ANGLE arc"
    this.eps_text += this.indent + x + " " + y + " " + radius + " " + rad2deg(startAngle) + " " + rad2deg(endAngle) + " arcn\n";
  } else {
    // command "X Y RADIUS START_ANGLE END_ANGLE arcn"
    this.eps_text += this.indent + x + " " + y + " " + radius + " " + rad2deg(startAngle) + " " + rad2deg(endAngle) + " arc\n";
  }
};

// imagine two lines, one going from the current position to point 1 and
// another going from point 1 to point 2. 
// Now imagine a circle of the given radius which touches the two lines at at 
// two tangental points T01 and T12. Add a line to the path that goes from
// the current position to T01 and add the arc which goes from T01 to T12.
// see http://www.dbp-consulting.com/tutorials/canvas/CanvasArcTo.html
EpsContext.prototype.arcTo = function(cpx1, cpy1, cpx2, cpy2, radius) {
  this.detect();
  // command "X1 Y1 X2 Y2 RADIUS arct"
  this.eps_text += this.indent + cpx1 + " " + cpy1 + " " + cpx2 + " " + cpy2 + " " + radius + " arct\n";
};

EpsContext.prototype.quadraticArcTo = function(cpx, cpy, x, y) {
  /* 
   For the equations below the following variable name prefixes are used: 
     qp0 is the quadratic curve starting point (you must keep this from your 
        last point sent to moveTo(), lineTo(), or bezierCurveTo() ). 
     qp1 is the quadratic curve control point (this is the cpx,cpy you would 
        have sent to quadraticCurveTo() ). 
     qp2 is the quadratic curve ending point (this is the x,y arguments you 
        would have sent to quadraticCurveTo() ). 
   We will convert these points to compute the two needed cubic control points 
    (the starting/ending points are the same for both 
   the quadratic and cubic curves. 
 
   The exact equations for the two cubic control points are: 
     cp0 = qp0 and cp3 = qp2 
     cp1 = qp0 + (qp1 - qp0) * ratio 
     cp2 = cp1 + (qp2 - qp0) * (1 - ratio) 
     where ratio = (sqrt(2) - 1) * 4 / 3 exactly (approx. 0.5522847498307933984022516322796) 
                   if the quadratic is an approximation of an elliptic arc, 
                      and the cubic must approximate the same arc, or 
           ratio = 2.0 / 3.0 for keeping the same quadratic curve. 
 
   In the code below, we must compute both the x and y terms for each point separately. 
 
    cp1x = qp0x + (qp1x - qp0x) * ratio; 
    cp1y = qp0y + (qp1y - qp0y) * ratio; 
    cp2x = cp1x + (qp2x - qp0x) * (1 - ratio); 
    cp2y = cp1y + (qp2y - qp0y) * (1 - ratio); 
 
   We will now  
     a) replace the qp0x and qp0y variables with currentX and currentY 
        (which *you* must store for each moveTo/lineTo/bezierCurveTo) 
     b) replace the qp1x and qp1y variables with cpx and cpy (which we would 
        have passed to quadraticCurveTo) 
     c) replace the qp2x and qp2y variables with x and y. 
   which leaves us with:  
  */  
  var ratio = 2.0 / 3.0; // 0.5522847498307933984022516322796 if the Bezier is 
                        // approximating an elliptic arc with best fitting  
  var cp1x = this.currentX + (cpx - this.currentX) * ratio;  
  var cp1y = this.currentY + (cpy - this.currentY) * ratio;  
  var cp2x = cp1x + (x - this.currentX) * (1 - ratio);  
  var cp2y = cp1y + (y - this.currentY) * (1 - ratio);  
  
  // and now call cubic Bezier curve to function   
  this.bezierCurveTo( cp1x, cp1y, cp2x, cp2y, x, y );
};

// add a bezier curve to the path
EpsContext.prototype.bezierCurveTo = function(cp1x, cp1y, cp2x, cp2y, x, y) {
  this.detect();
  // command "CP1X CP1Y CP2X CP2Y X Y curveto"
  this.eps_text += this.indent + cp1x + " " + cp1y + " " + cp2x + " " + cp2y + " " + x + " " + y + " curveto";
};

// stroke the current path
EpsContext.prototype.stroke = function() {
  this.detect();
  this.activateStyle(false);
  // stroke
  this.eps_text += this.indent + "stroke\n";
};

// fill the current path
EpsContext.prototype.fill = function() {
  this.detect();
  this.activateStyle(true);
  // fill
  this.eps_text += this.indent + "fill\n";
};

// create a clipping region from the current path
EpsContext.prototype.clip = function() {
  this.detect();
  // clip
  this.eps_text += this.indent + "clip\n";
};

// Draws a filled rectangle
EpsContext.prototype.fillRect = function(x, y, width, height) {
  this.detect();
  this.activateStyle(true);
  // rectfill
  this.eps_text += this.indent + x + " " + y + " " + width + " " + height + " rectfill\n";
};

// Draws a rectangular outline
EpsContext.prototype.strokeRect = function(x, y, width, height) {
  this.detect();
  this.activateStyle(false);
  // rectstroke
  this.eps_text += this.indent + x + " " + y + " " + width + " " + height + " rectstroke\n";
};

// Clears the specified area and makes it transparent.
EpsContext.prototype.clearRect = function(x, y, width, height) {
  this.detect();
  // fill a rectangle with white in the cleared region
  // EPS doesn't do transparency so this is as close as it can get
  // command "1 setgray X Y WIDTH HEIGHT rectfill"
  this.eps_text += this.indent + "gsave\n";
  this.eps_text += this.indent + "  1 setgray\n";
  this.eps_text += this.indent + "  " + x + " " + y + " " + width + " " + height + " rectfill\n";
  this.eps_text += this.indent + "grestore\n";
};

// Draws a filled string
EpsContext.prototype.fillText = function(string, x, y) {
  var state = this.current_state;
  this.detect();
  this.activateStyle(true);
  // lookup the font with "/FONT_NAME findfont" (leaves FONT on stack)
  // scale the font with "FONT SIZE scalefont" (leaves FONT on stack
  // set the font with "FONT setfont"
  // move the printing location with "X Y moveto"
  // various measurement commands will be needed to do center alignment etc
  // like "stringwidth"
  // show the text with "(STRING) show"
  string = string.replace(/\\/g, "\\\\");
  string = string.replace(/\(/g, "\\(");
  string = string.replace(/\)/g, "\\)");
  this.save();
  this.translate(x, y);
  this.scale(1, -1);
  this.eps_text += this.indent + "(" + string + ")\n";
  if (state.textBaseline != "alphabetic") {
    this.beginPath();
    this.moveTo(0,0);
    this.eps_text += this.indent + "dup true charpath flattenpath pathbbox %bounding box\n";
    this.eps_text += this.indent + "/ascent exch def pop /decent exch def pop\n";
    if (state.textBaseline == "top") {
      this.eps_text += this.indent + "0 ascent neg translate %vertical align top\n";
    } else if (state.textBaseline == "middle") {
      this.eps_text += this.indent + "0 ascent decent sub 2 div decent sub neg translate %vertical align middle\n";
    } else if (state.textBaseline == "bottom") {
      this.eps_text += this.indent + "0 decent neg translate %vertical align bottom\n";
    }
  }
  if (state.textAlign == "right") {
    this.eps_text += this.indent + "dup stringwidth pop neg 0 translate %right align\n"  
  } else if (state.textAlign == "center") {
    this.eps_text += this.indent + "dup stringwidth pop 2 div neg 0 translate %center align\n"  
  }
  this.moveTo(0,0);
  this.eps_text += this.indent + "show\n";
  this.restore();
};

// move the canvas origin
EpsContext.prototype.translate = function(dx, dy) {
  this.detect();
  // command "DELTA_X DELTA_Y translate"
  this.eps_text += this.indent + dx + " " + dy + " translate\n";
};

// rotate around the canvas origin
EpsContext.prototype.rotate = function(angle) {
  this.detect();
  // command "DEGREES rotate" (note need to convert angle from radians to degrees)
  this.eps_text += this.indent + rad2deg(angle) + " rotate\n";
};

// scale 
EpsContext.prototype.scale = function(scale_x, scale_y) {
  this.detect();
  // command "SCALE_X SCALE_Y scale
  this.eps_text += this.indent + scale_x + " " + scale_y + " scale\n";
};

// multiplies the current transform matrix by the matrix described by:
// m11    m21   dx
// m12    m22   dy
// 0      0     1
EpsContext.prototype.transform = function(m11, m12, m21, m22, dx, dy) {
  this.detect();
  // command "[M11 M12 M21 M22 DX DY] concat"
  this.eps_text += this.indent + "[" + m11 + " " + m12 + " " + m21 + " " + m22 + " " + dx + " " + dy + "] concat\n";
};

// reset transform matrix to the identity matrix then call transform
EpsContext.prototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
  // command:
  // "[1 0 0 1 0 0] defaultmatrix setmatrix 0 height translate 1 -1 scale "
  // "[M11 M12 M21 M22 DX DY] concat"
  this.eps_text += this.indent + "[1 0 0 1 0 0] defaultmatrix setmatrix\n";
  this.eps_text += this.indent + "0 " + this.height + " translate\n";
  this.eps_text += this.indent + "1 -1 scale\n";
  this.transform(m11, m12, m21, m22, dx, dy);
};

EpsContext.prototype.register_font = function(font_str, eps_font, size) {
  font_str = font_str.replace(/\s+/g, ""); // remove spaces
  font_str = font_str.toLowerCase(); // lower case
  this.font_lookup[font_str] = {'name': eps_font, 'size': size};
};

EpsContext.prototype.lookup_font = function(font_str) {
  font_str = font_str.replace(/\s+/g, ""); // remove spaces
  font_str = font_str.toLowerCase(); // lower case
  return this.font_lookup[font_str];
};

// return the EPS text
EpsContext.prototype.eps = function() {
  return this.eps_text + this.indent + "showpage\n";
};

    </script>
    <script type="text/javascript">
      // 
      // return true if any part of the passed element is visible in the viewport
      //
      function element_in_viewport(elem) {
        var rect;
        try {
          rect = elem.getBoundingClientRect();
        } catch (e) {
          return false;
        }
        return (
            rect.top < (window.innerHeight || document.body.clientHeight) &&
            rect.bottom > 0 &&
            rect.left < (window.innerWidth || document.body.clientWidth) &&
            rect.right > 0
            );
      }

      //
      // Functions to delay a drawing task until it is required or it would not lag the display to do so
      //

      // a list of items still to be drawn
      var drawable_list = [];
      // the delay between drawing objects that are not currently visible
      var draw_delay = 1;
      // the delay after a user interaction
      var user_delay = 300;
      // the delay after a user has stopped scrolling and is viewing the stuff drawn on the current page
      var stop_delay = 300;
      // the timer handle; allows resetting of the timer after user interactions
      var draw_timer = null;

      //
      // Drawable
      //
      // elem - a page element which defines the position on the page that drawing is to be done
      // task - an object with the method run which takes care of painting the object
      //
      var Drawable = function(elem, task) {
        this.elem = elem;
        this.task = task;
      }

      //
      // Drawable.is_visible
      //
      // Determines if the element is visible in the viewport
      //
      Drawable.prototype.is_visible = function() {
        return element_in_viewport(this.elem);
      }

      //
      // Drawable.run
      //
      // Run the task held by the drawable
      Drawable.prototype.run = function() {
        if (this.task) this.task.run();
        this.task = null;
      }

      //
      // Drawable.run
      //
      // Run the task iff visible
      // returns true if the task ran or has already run
      Drawable.prototype.run_visible = function() {
        if (this.task) {
          if (element_in_viewport(this.elem)) {
            this.task.run();
            this.task = null;
            return true;
          }
          return false;
        } else {
          return true;
        }
      }

      //
      // draw_on_screen
      //
      // Checks each drawable object and draws those on screen.
      //
      function draw_on_screen() {
        var found = false;
        for (var i = 0; i < drawable_list.length; i++) {
          if (drawable_list[i].run_visible()) {
            drawable_list.splice(i--, 1);
            found = true;
          }
        }
        return found;
      }

      //
      // process_draw_tasks
      //
      // Called on a delay to process the next available
      // draw task.
      //
      function process_draw_tasks() {
        var delay = draw_delay;
        draw_timer = null;
        if (drawable_list.length == 0) return; //no more tasks
        if (draw_on_screen()) {
          delay = stop_delay; //give the user a chance to scroll
        } else {
          //get next task
          var drawable = drawable_list.shift();
          drawable.task.run();
        }
        //allow UI updates between tasks
        draw_timer = window.setTimeout("process_draw_tasks()", delay);
      }

      //
      // delayed_process_draw_tasks
      //
      // Call process_draw_tasks after a short delay.
      // The delay serves to group multiple redundant events.       
      // Should be set as event handler for onscroll and onresize.
      //
      function delayed_process_draw_tasks() {
        //reset the timer
        if (drawable_list.length > 0) { 
          if (draw_timer != null) clearTimeout(draw_timer);
          draw_timer = window.setTimeout("process_draw_tasks()", user_delay);
        }
      }

      //
      // add_draw_task
      //
      // Add a drawing task to be called immediately if it is
      // visible, or to be called on a delay to reduce stuttering
      // effect on the web browser.
      function add_draw_task(elem, task) {
        drawable = new Drawable(elem, task);
        if (drawable.is_visible()) {
          task.run();
        } else {
          drawable_list.push(drawable);
          //reset timer
          if (draw_timer != null) clearTimeout(draw_timer);
          draw_timer = window.setTimeout("process_draw_tasks()", user_delay);
        }
      }


    </script>
    <script type="text/javascript">
function motif_logo_template(inputs) {
  function _input(name) {
    if (typeof inputs[name] === "undefined") {
      throw new Error("Missing template variable: " + name);
    }
    return inputs[name];
  }
  return (
"%!PS-Adobe-3.0 EPSF-3.0\n" +
"%%Title: Sequence Logo : " + _input("TITLE") + "\n" +
"%%Creator: " + _input("CREATOR") + "\n" +
"%%CreationDate: " + _input("CREATIONDATE") + "\n" +
"%%BoundingBox:   0  0  " + _input("BOUNDINGWIDTH") + " " + _input("BOUNDINGHEIGHT") + " \n" +
"%%Pages: 0\n" +
"%%DocumentFonts: \n" +
"%%EndComments\n" +
"\n" +
"% ---- CONSTANTS ----\n" +
"\/cmfactor 72 2.54 div def % defines points -> cm conversion\n" +
"\/cm {cmfactor mul} bind def % defines centimeters\n" +
"\n" +
"% ---- VARIABLES ----\n" +
"\n" +
"% NA = Nucleic Acid, AA = Amino Acid\n" +
"\/logoType (" + _input("LOGOTYPE") + ") def \n" +
"\n" +
"\/logoTitle (" + _input("TITLE") + ") def\n" +
"\n" +
"% Dimensions in cm\n" +
"\/logoWidth " + _input("LOGOWIDTH") + " cm def\n" +
"\/logoHeight " + _input("LOGOLINEHEIGHT") + " cm def\n" +
"\/totalHeight " + _input("LOGOHEIGHT") + " cm def\n" +
"\n" +
"\/yaxis " + _input("YAXIS") + " def\n" +
"\/yaxisLabel (" + _input("YAXISLABEL") + ") def\n" +
"\/yaxisBits  " + _input("BARBITS") + " def % bits\n" +
"\/yaxisTicBits " + _input("TICBITS") + " def\n" +
"\n" +
"\/xaxis " + _input("NUMBERING") + " def\n" +
"\/xaxisLabel (" + _input("XAXISLABEL") + ") def\n" +
"\/showEnds (" + _input("SHOWENDS") + ") def \n" +
"\n" +
"\/showFineprint true def\n" +
"\/fineprint (" + _input("FINEPRINT") + ") def\n" +
"\n" +
"\/charsPerLine " + _input("CHARSPERLINE") + " def\n" +
"\n" +
"\/showingBox " + _input("SHOWINGBOX") + " def    \n" +
"\/shrinking false def   % true falses\n" +
"\/shrink  1.0 def\n" +
"\/outline " + _input("OUTLINE") + " def\n" +
"\n" +
"\/IbeamFraction  " + _input("ERRORBARFRACTION") + " def\n" +
"\/IbeamGray      0.50 def\n" +
"\/IbeamLineWidth 0.5 def\n" +
"\n" +
"\/fontsize       " + _input("FONTSIZE") + " def\n" +
"\/titleFontsize  " + _input("TITLEFONTSIZE") + " def\n" +
"\/smallFontsize  " + _input("SMALLFONTSIZE") + " def\n" +
"\n" +
"\/topMargin      " + _input("TOPMARGIN") + " cm def\n" +
"\/bottomMargin   " + _input("BOTTOMMARGIN") + " cm def\n" +
"\n" +
"\/defaultColor [0 0 0] def \n" +
"\n" +
_input("COLORDICT") + "\n" +
"\n" +
"\/colorDict fullColourDict def\n" +
"\n" +
"% ---- DERIVED PARAMETERS ----\n" +
"\n" +
"\/leftMargin\n" +
"  fontsize 3.5 mul\n" +
"\n" +
"def \n" +
"\n" +
"\/rightMargin \n" +
"  %Add extra room if showing ends\n" +
"  showEnds (false) eq { fontsize}{fontsize 1.5 mul} ifelse\n" +
"def\n" +
"\n" +
"\/yaxisHeight \n" +
"  logoHeight \n" +
"  bottomMargin sub  \n" +
"  topMargin sub\n" +
"def\n" +
"\n" +
"\/ticWidth fontsize 2 div def\n" +
"\n" +
"\/pointsPerBit yaxisHeight yaxisBits div  def\n" +
"\n" +
"\/stackMargin 1 def\n" +
"\n" +
"% Do not add space aroung characters if characters are boxed\n" +
"\/charRightMargin \n" +
"  showingBox { 0.0 } {stackMargin} ifelse\n" +
"def\n" +
"\n" +
"\/charTopMargin \n" +
"  showingBox { 0.0 } {stackMargin} ifelse\n" +
"def\n" +
"\n" +
"\/charWidth\n" +
"  logoWidth\n" +
"  leftMargin sub\n" +
"  rightMargin sub\n" +
"  charsPerLine div\n" +
"  charRightMargin sub\n" +
"def\n" +
"\n" +
"\/charWidth4 charWidth 4 div def\n" +
"\/charWidth2 charWidth 2 div def\n" +
"\n" +
"\/stackWidth \n" +
"  charWidth charRightMargin add\n" +
"def\n" +
" \n" +
"\/numberFontsize \n" +
"  fontsize charWidth lt {fontsize}{charWidth} ifelse\n" +
"def\n" +
"\n" +
"% movements to place 5'\/N and 3'\/C symbols\n" +
"\/leftEndDeltaX  fontsize neg         def\n" +
"\/leftEndDeltaY  fontsize 1.5 mul neg def\n" +
"\/rightEndDeltaX fontsize 0.25 mul     def\n" +
"\/rightEndDeltaY leftEndDeltaY        def\n" +
"\n" +
"% Outline width is proporional to charWidth, \n" +
"% but no less that 1 point\n" +
"\/outlinewidth \n" +
"  charWidth 32 div dup 1 gt  {}{pop 1} ifelse\n" +
"def\n" +
"\n" +
"\n" +
"% ---- PROCEDURES ----\n" +
"\n" +
"\/StartLogo { \n" +
"  % Save state\n" +
"  save \n" +
"  gsave \n" +
"\n" +
"  % Print Logo Title, top center \n" +
"  gsave \n" +
"    SetStringFont\n" +
"\n" +
"    logoWidth 2 div\n" +
"    logoTitle\n" +
"    stringwidth pop 2 div sub\n" +
"    totalHeight\n" +
"    titleFontsize sub\n" +
"    moveto\n" +
"\n" +
"    logoTitle\n" +
"    show\n" +
"  grestore\n" +
"\n" +
"  % Print X-axis label, bottom center\n" +
"  gsave\n" +
"    SetStringFont\n" +
"\n" +
"    logoWidth 2 div\n" +
"    xaxisLabel\n" +
"    stringwidth pop 2 div sub\n" +
"    0\n" +
"    titleFontsize 3 div\n" +
"    add\n" +
"    moveto\n" +
"\n" +
"    xaxisLabel\n" +
"    show\n" +
"  grestore\n" +
"\n" +
"  % Show Fine Print\n" +
"  showFineprint {\n" +
"    gsave\n" +
"      SetSmallFont\n" +
"      logoWidth\n" +
"        fineprint stringwidth pop sub\n" +
"        smallFontsize sub\n" +
"          smallFontsize 3 div\n" +
"      moveto\n" +
"    \n" +
"      fineprint show\n" +
"    grestore\n" +
"  } if\n" +
"\n" +
"  % Move to lower left corner of last line, first stack\n" +
"  leftMargin bottomMargin translate\n" +
"\n" +
"  % Move above first line ready for StartLine \n" +
"  0 totalHeight translate\n" +
"\n" +
"  SetLogoFont\n" +
"} bind def\n" +
"\n" +
"\/EndLogo { \n" +
"  grestore \n" +
"  showpage \n" +
"  restore \n" +
"} bind def\n" +
"\n" +
"\n" +
"\/StartLine { \n" +
"  % move down to the bottom of the line:\n" +
"  0 logoHeight neg translate\n" +
"  \n" +
"  gsave \n" +
"    yaxis { MakeYaxis } if\n" +
"    xaxis { showEnds (true) eq {ShowLeftEnd} if } if\n" +
"} bind def\n" +
"\n" +
"\/EndLine{ \n" +
"    xaxis { showEnds (true) eq {ShowRightEnd} if } if\n" +
"  grestore \n" +
"} bind def\n" +
"\n" +
"\n" +
"\/MakeYaxis {\n" +
"  gsave    \n" +
"    stackMargin neg 0 translate\n" +
"    ShowYaxisBar\n" +
"    ShowYaxisLabel\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowYaxisBar { \n" +
"  gsave  \n" +
"    SetStringFont\n" +
"\n" +
"    \/str 10 string def % string to hold number  \n" +
"    \/smallgap stackMargin 2 div def\n" +
"\n" +
"    % Draw first tic and bar\n" +
"    gsave    \n" +
"      ticWidth neg 0 moveto \n" +
"      ticWidth 0 rlineto \n" +
"      0 yaxisHeight rlineto\n" +
"      stroke\n" +
"    grestore\n" +
"\n" +
"   \n" +
"    % Draw the tics\n" +
"    % initial increment limit proc for\n" +
"    0 yaxisTicBits yaxisBits abs %cvi\n" +
"    {\/loopnumber exch def\n" +
"\n" +
"      % convert the number coming from the loop to a string\n" +
"      % and find its width\n" +
"      loopnumber 10 str cvrs\n" +
"      \/stringnumber exch def % string representing the number\n" +
"\n" +
"      stringnumber stringwidth pop\n" +
"      \/numberwidth exch def % width of number to show\n" +
"\n" +
"      \/halfnumberheight\n" +
"         stringnumber CharBoxHeight 2 div\n" +
"      def\n" +
"\n" +
"      numberwidth % move back width of number\n" +
"      neg loopnumber pointsPerBit mul % shift on y axis\n" +
"      halfnumberheight sub % down half the digit\n" +
"\n" +
"      moveto % move back the width of the string\n" +
"\n" +
"      ticWidth neg smallgap sub % Move back a bit more  \n" +
"      0 rmoveto % move back the width of the tic  \n" +
"\n" +
"      stringnumber show\n" +
"      smallgap 0 rmoveto % Make a small gap  \n" +
"\n" +
"      % now show the tic mark\n" +
"      0 halfnumberheight rmoveto % shift up again\n" +
"      ticWidth 0 rlineto\n" +
"      stroke\n" +
"    } for\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\/ShowYaxisLabel {\n" +
"  gsave\n" +
"    SetStringFont\n" +
"\n" +
"    % How far we move left depends on the size of\n" +
"    % the tic labels.\n" +
"    \/str 10 string def % string to hold number  \n" +
"    yaxisBits yaxisTicBits div cvi yaxisTicBits mul \n" +
"    str cvs stringwidth pop\n" +
"    ticWidth 1.5 mul  add neg  \n" +
"\n" +
"\n" +
"    yaxisHeight\n" +
"    yaxisLabel stringwidth pop\n" +
"    sub 2 div\n" +
"\n" +
"    translate\n" +
"    90 rotate\n" +
"    0 0 moveto\n" +
"    yaxisLabel show\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/StartStack {  % <stackNumber> startstack\n" +
"  xaxis {MakeNumber}{pop} ifelse\n" +
"  gsave\n" +
"} bind def\n" +
"\n" +
"\/EndStack {\n" +
"  grestore\n" +
"  stackWidth 0 translate\n" +
"} bind def\n" +
"\n" +
"\n" +
"% Draw a character whose height is proportional to symbol bits\n" +
"\/MakeSymbol{ % charbits character MakeSymbol\n" +
"  gsave\n" +
"    \/char exch def\n" +
"    \/bits exch def\n" +
"\n" +
"    \/bitsHeight \n" +
"       bits pointsPerBit mul \n" +
"    def\n" +
"\n" +
"    \/charHeight \n" +
"       bitsHeight charTopMargin sub\n" +
"       dup \n" +
"       0.0 gt {}{pop 0.0} ifelse % if neg replace with zero \n" +
"    def \n" +
" \n" +
"    charHeight 0.0 gt {\n" +
"      char SetColor\n" +
"      charWidth charHeight char ShowChar\n" +
"\n" +
"      showingBox { % Unfilled box\n" +
"        0 0 charWidth charHeight false ShowBox\n" +
"      } if\n" +
"\n" +
"\n" +
"    } if\n" +
"\n" +
"  grestore\n" +
"\n" +
"  0 bitsHeight translate \n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowChar { % <width> <height> <char> ShowChar\n" +
"  gsave\n" +
"    \/tc exch def    % The character\n" +
"    \/ysize exch def % the y size of the character\n" +
"    \/xsize exch def % the x size of the character\n" +
"\n" +
"    \/xmulfactor 1 def \n" +
"    \/ymulfactor 1 def\n" +
"    \/limmulfactor 0.01 def\n" +
"    \/drawable true def\n" +
"\n" +
"  \n" +
"    % if ysize is negative, make everything upside down!\n" +
"    ysize 0 lt {\n" +
"      % put ysize normal in this orientation\n" +
"      \/ysize ysize abs def\n" +
"      xsize ysize translate\n" +
"      180 rotate\n" +
"    } if\n" +
"\n" +
"    shrinking {\n" +
"      xsize 1 shrink sub 2 div mul\n" +
"        ysize 1 shrink sub 2 div mul translate \n" +
"\n" +
"      shrink shrink scale\n" +
"    } if\n" +
"\n" +
"    % Calculate the font scaling factors\n" +
"    % Loop twice to catch small correction due to first scaling\n" +
"    2 {\n" +
"      gsave\n" +
"        xmulfactor ymulfactor scale\n" +
"      \n" +
"        ysize % desired size of character in points\n" +
"        tc CharBoxHeight \n" +
"        dup 0.0 ne {\n" +
"          div % factor by which to scale up the character\n" +
"          \/ymulfactor exch def\n" +
"        } % end if\n" +
"        {pop pop}\n" +
"        ifelse\n" +
"\n" +
"        xsize % desired size of character in points\n" +
"        tc CharBoxWidth  \n" +
"        dup 0.0 ne {\n" +
"          div % factor by which to scale up the character\n" +
"          \/xmulfactor exch def\n" +
"        } % end if\n" +
"        {pop pop}\n" +
"        ifelse\n" +
"      grestore\n" +
"      % if the multiplication factors get too small we need to avoid a crash\n" +
"      xmulfactor limmulfactor lt {\n" +
"        \/xmulfactor 1 def\n" +
"        \/drawable false def\n" +
"      } if\n" +
"      ymulfactor limmulfactor lt {\n" +
"        \/ymulfactor 1 def\n" +
"        \/drawable false def\n" +
"      } if\n" +
"    } repeat\n" +
"\n" +
"    % Adjust horizontal position if the symbol is an I\n" +
"    tc (I) eq {\n" +
"      charWidth 2 div % half of requested character width\n" +
"      tc CharBoxWidth 2 div % half of the actual character\n" +
"      sub 0 translate\n" +
"      % Avoid x scaling for I \n" +
"      \/xmulfactor 1 def \n" +
"    } if\n" +
"\n" +
"\n" +
"    % ---- Finally, draw the character\n" +
"    drawable { \n" +
"      newpath\n" +
"      xmulfactor ymulfactor scale\n" +
"\n" +
"      % Move lower left corner of character to start point\n" +
"      tc CharBox pop pop % llx lly : Lower left corner\n" +
"      exch neg exch neg\n" +
"      moveto\n" +
"\n" +
"      outline {  % outline characters:\n" +
"        outlinewidth setlinewidth\n" +
"        tc true charpath\n" +
"        gsave 1 setgray fill grestore\n" +
"        clip stroke\n" +
"      } { % regular characters\n" +
"        tc show\n" +
"      } ifelse\n" +
"    } if\n" +
"\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowBox { % x1 y1 x2 y2 filled ShowBox\n" +
"  gsave\n" +
"    \/filled exch def \n" +
"    \/y2 exch def\n" +
"    \/x2 exch def\n" +
"    \/y1 exch def\n" +
"    \/x1 exch def\n" +
"    newpath\n" +
"    x1 y1 moveto\n" +
"    x2 y1 lineto\n" +
"    x2 y2 lineto\n" +
"    x1 y2 lineto\n" +
"    closepath\n" +
"\n" +
"    clip\n" +
"    \n" +
"    filled {\n" +
"      fill\n" +
"    }{ \n" +
"      0 setgray stroke   \n" +
"    } ifelse\n" +
"\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/MakeNumber { % number MakeNumber\n" +
"  gsave\n" +
"    SetNumberFont\n" +
"    stackWidth 0 translate\n" +
"    90 rotate % rotate so the number fits\n" +
"    dup stringwidth pop % find the length of the number\n" +
"    neg % prepare for move\n" +
"    stackMargin sub % Move back a bit\n" +
"    charWidth (0) CharBoxHeight % height of numbers\n" +
"    sub 2 div %\n" +
"    moveto % move back to provide space\n" +
"    show\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/Ibeam{ % heightInBits Ibeam\n" +
"  gsave\n" +
"    % Make an Ibeam of twice the given height in bits\n" +
"    \/height exch  pointsPerBit mul def \n" +
"    \/heightDRAW height IbeamFraction mul def\n" +
"\n" +
"    IbeamLineWidth setlinewidth\n" +
"    IbeamGray setgray \n" +
"\n" +
"    charWidth2 height neg translate\n" +
"    ShowIbar\n" +
"    newpath\n" +
"      0 0 moveto\n" +
"      0 heightDRAW rlineto\n" +
"    stroke\n" +
"    newpath\n" +
"      0 height moveto\n" +
"      0 height rmoveto\n" +
"      currentpoint translate\n" +
"    ShowIbar\n" +
"    newpath\n" +
"    0 0 moveto\n" +
"    0 heightDRAW neg rlineto\n" +
"    currentpoint translate\n" +
"    stroke\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowIbar { % make a horizontal bar\n" +
"  gsave\n" +
"    newpath\n" +
"      charWidth4 neg 0 moveto\n" +
"      charWidth4 0 lineto\n" +
"    stroke\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowLeftEnd {\n" +
"  gsave\n" +
"    SetStringFont\n" +
"    leftEndDeltaX leftEndDeltaY moveto\n" +
"    logoType (NA) eq {(5) show ShowPrime} if\n" +
"    logoType (AA) eq {(N) show} if\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowRightEnd { \n" +
"  gsave\n" +
"    SetStringFont\n" +
"    rightEndDeltaX rightEndDeltaY moveto\n" +
"    logoType (NA) eq {(3) show ShowPrime} if\n" +
"    logoType (AA) eq {(C) show} if\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"\/ShowPrime {\n" +
"  gsave\n" +
"    SetPrimeFont\n" +
"    (\\242) show \n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
" \n" +
"\/SetColor{ % <char> SetColor\n" +
"  dup colorDict exch known {\n" +
"    colorDict exch get aload pop setrgbcolor\n" +
"  } {\n" +
"    pop\n" +
"    defaultColor aload pop setrgbcolor\n" +
"  } ifelse \n" +
"} bind def\n" +
"\n" +
"% define fonts\n" +
"\/SetTitleFont {\/Times-Bold findfont titleFontsize scalefont setfont} bind def\n" +
"\/SetLogoFont  {\/Helvetica-Bold findfont charWidth  scalefont setfont} bind def\n" +
"\/SetStringFont{\/Helvetica-Bold findfont fontsize scalefont setfont} bind def\n" +
"\/SetPrimeFont {\/Symbol findfont fontsize scalefont setfont} bind def\n" +
"\/SetSmallFont {\/Helvetica findfont smallFontsize scalefont setfont} bind def\n" +
"\n" +
"\/SetNumberFont {\n" +
"    \/Helvetica-Bold findfont \n" +
"    numberFontsize\n" +
"    scalefont\n" +
"    setfont\n" +
"} bind def\n" +
"\n" +
"%Take a single character and return the bounding box\n" +
"\/CharBox { % <char> CharBox <lx> <ly> <ux> <uy>\n" +
"  gsave\n" +
"    newpath\n" +
"    0 0 moveto\n" +
"    % take the character off the stack and use it here:\n" +
"    true charpath \n" +
"    flattenpath \n" +
"    pathbbox % compute bounding box of 1 pt. char => lx ly ux uy\n" +
"    % the path is here, but toss it away ...\n" +
"  grestore\n" +
"} bind def\n" +
"\n" +
"\n" +
"% The height of a characters bounding box\n" +
"\/CharBoxHeight { % <char> CharBoxHeight <num>\n" +
"  CharBox\n" +
"  exch pop sub neg exch pop\n" +
"} bind def\n" +
"\n" +
"\n" +
"% The width of a characters bounding box\n" +
"\/CharBoxWidth { % <char> CharBoxHeight <num>\n" +
"  CharBox\n" +
"  pop exch pop sub neg \n" +
"} bind def\n" +
"\n" +
"% Set the colour scheme to be faded to indicate trimming\n" +
"\/MuteColour {\n" +
"  \/colorDict mutedColourDict def\n" +
"} def\n" +
"\n" +
"% Restore the colour scheme to the normal colours\n" +
"\/RestoreColour {\n" +
"  \/colorDict fullColourDict def\n" +
"} def\n" +
"\n" +
"% Draw the background for a trimmed section\n" +
"% takes the number of columns as a parameter\n" +
"\/DrawTrimBg { % <num> DrawTrimBox\n" +
"  \/col exch def\n" +
"  \n" +
"  \/boxwidth \n" +
"    col stackWidth mul \n" +
"  def\n" +
" \n" +
"  gsave\n" +
"    0.97 setgray\n" +
"\n" +
"    newpath\n" +
"    0 0 moveto\n" +
"    boxwidth 0 rlineto\n" +
"    0 yaxisHeight rlineto\n" +
"    0 yaxisHeight lineto\n" +
"    closepath\n" +
"    \n" +
"    fill\n" +
"  grestore\n" +
"} def\n" +
"\n" +
"\/DrawTrimEdge {\n" +
"  gsave\n" +
"    0.2 setgray\n" +
"    [2] 0 setdash\n" +
"\n" +
"    newpath\n" +
"    0 0 moveto\n" +
"    0 yaxisHeight lineto\n" +
"    \n" +
"    stroke\n" +
"\n" +
"} def\n" +
"\n" +
"\n" +
"% Deprecated names\n" +
"\/startstack {StartStack} bind  def\n" +
"\/endstack {EndStack}     bind def\n" +
"\/makenumber {MakeNumber} bind def\n" +
"\/numchar { MakeSymbol }  bind def\n" +
"\n" +
"%%EndProlog\n" +
"\n" +
"%%Page: 1 1\n" +
"StartLogo\n" +
"\n" +
_input("DATA") + "\n" +
"\n" +
"EndLogo\n" +
"\n" +
"%%EOF\n"
  );
}
    </script>
    <script type="text/javascript">
//======================================================================
// start Alphabet object
//======================================================================
var Alphabet = function(alphabet, background) {
  "use strict";
  var i, j, sym, aliases, complement, comp_e_sym, ambigs, generate_background;
  generate_background = (background == null);
  if (generate_background) {
    background = [];
    for (i = 0; i < alphabet.ncore; i++) background[i] = 1.0 / alphabet.ncore;
  } else if (alphabet.ncore != background.length) {
    throw new Error("The background length does not match the alphabet length.");
  }
  this.name = alphabet.name;
  this.like = (alphabet.like != null ? alphabet.like.toUpperCase() : null);
  this.ncore = alphabet.ncore;
  this.symbols = alphabet.symbols;
  this.background = background;
  this.genbg = generate_background;
  this.encode = {};
  this.encode2core = {};
  this.complement = {};
  // check if all symbols are same case
  var seen_uc = false;
  var seen_lc = false;
  var check_case = function (syms) {
    var s, sym;
    if (typeof syms === "string") {
      for (s = 0; s < syms.length; s++) {
        sym = syms.charAt(s);
        if (sym >= 'a' && sym <= 'z') seen_lc = true;
        else if (sym >= 'A' && sym <= 'Z') seen_uc = true;
      }
    }
  };
  for (i = 0; i < this.symbols.length; i++) {
    check_case(this.symbols[i].symbol);
    check_case(this.symbols[i].aliases);
  }
  // now map symbols to indexes
  var update_array = function(array, syms, index) {
    var s, sym;
    if (typeof syms === "string") {
      for (s = 0; s < syms.length; s++) {
        sym = syms.charAt(s);
        array[sym] = index;
        // when only a single case is used, then encode as case insensitive
        if (seen_uc != seen_lc) {
          if (sym >= 'a' && sym <= 'z') {
            array[sym.toUpperCase()] = index;
          } else if (sym >= 'A' && sym <= 'Z') {
            array[sym.toLowerCase()] = index;
          }
        }
      }
    }
  }
  // map core symbols to index
  for (i = 0; i < this.ncore; i++) {
    update_array(this.encode2core, this.symbols[i].symbol, i);
    update_array(this.encode, this.symbols[i].symbol, i);
    update_array(this.encode2core, this.symbols[i].aliases, i);
    update_array(this.encode, this.symbols[i].aliases, i);
  }
  // map ambiguous symbols to index
  ambigs = {};
  for (i = this.ncore; i < this.symbols.length; i++) {
    update_array(this.encode, this.symbols[i].symbol, i);
    update_array(this.encode, this.symbols[i].aliases, i);
    ambigs[this.symbols[i].equals] = i;
  }
  // determine complements
  for (i = 0; i < this.ncore; i++) {
    complement = this.symbols[i].complement;
    if (typeof complement === "string") {
      this.complement[i] = this.encode2core[complement];
    }
  }
  next_symbol:
  for (i = this.ncore; i < this.symbols.length; i++) {
    complement = "";
    for (j = 0; j < this.symbols[i].equals.length; j++) {
      comp_e_sym = this.complement[this.encode2core[this.symbols[i].equals.charAt(j)]];
      if (typeof comp_e_sym !== "number") continue next_symbol;
      complement += this.symbols[comp_e_sym].symbol;
    }
    complement = complement.split("").sort().join("");
    if (typeof ambigs[complement] === "number") {
      this.complement[i] = ambigs[complement];
    }
  }
  // determine case insensitivity
  this.case_insensitive = true;
  if (seen_uc == seen_lc) {
    // when there is a mixture of cases it probably won't
    // be case insensitive but we still need to check
    loop:
    for (i = 0; i < this.symbols.length; i++) {
      sym = this.symbols[i].symbol;
      if (sym >= 'A' && sym <= 'Z') {
        if (this.encode[sym.toLowerCase()] != i) {
          this.case_insensitive = false;
          break loop;
        }
      } else if (sym >= 'a' && sym <= 'z') {
        if (this.encode[sym.toUpperCase()] != i) {
          this.case_insensitive = false;
          break loop;
        }
      }
      aliases = this.symbols[i].aliases;
      if (aliases != null) {
        for (j = 0; j < aliases.length; j++) {
          sym = aliases.charAt(j);
          if (sym >= 'A' && sym <= 'Z') {
            if (this.encode[sym.toLowerCase()] != i) {
              this.case_insensitive = false;
              break loop;
            }
          } else if (sym >= 'a' && sym <= 'z') {
            if (this.encode[sym.toUpperCase()] != i) {
              this.case_insensitive = false;
              break loop;
            }
          }
        }
      }
    }
  }
  // normalise aliases to remove the prime symbol and eliminate
  // the alternate cases when the alphabet is case insensitive
  var seen, out;
  for (i = 0; i < this.symbols.length; i++) {
    sym = this.symbols[i].symbol;
    aliases = this.symbols[i].aliases;
    if (typeof aliases != "string") aliases = "";
    seen = {};
    out = [];
    if (this.case_insensitive) {
      sym = sym.toUpperCase();
      aliases = aliases.toUpperCase();
    }
    seen[sym] = true;
    for (j = 0; j < aliases.length; j++) {
      if (!seen[aliases.charAt(j)]) {
        seen[aliases.charAt(j)] = true;
        out.push(aliases.charAt(j));
      }
    }
    this.symbols[i].aliases = out.sort().join("");
  }
};
// return the name of the alphabet
Alphabet.prototype.get_alphabet_name = function() {
  return this.name;
};
// return if the alphabet can be complemented
Alphabet.prototype.has_complement = function() {
  return (typeof this.symbols[0].complement === "string");
};
// return true if an uppercase letter has the same meaning as the lowercase form
Alphabet.prototype.is_case_insensitive = function() {
  return this.case_insensitive;
};
// return the information content of an alphabet letter
Alphabet.prototype.get_ic = function() {
  return Math.log(this.ncore) / Math.LN2;
};
// return the count of the core alphabet symbols
Alphabet.prototype.get_size_core = function() {
  return this.ncore;
};
// return the count of all alphabet symbols
Alphabet.prototype.get_size_full = function() {
  return this.symbols.length;
};
// return the symbol for the given alphabet index
Alphabet.prototype.get_symbol = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  return this.symbols[alph_index].symbol;
};
// return the aliases for the given alphabet index
Alphabet.prototype.get_aliases = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  var sym_obj = this.symbols[alph_index];
  return (sym_obj.aliases != null ? sym_obj.aliases : "");
};
// return the name for the given alphabet index
Alphabet.prototype.get_name = function(alph_index) {
  "use strict";
  var sym;
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("Alphabet index out of bounds");
  }
  sym = this.symbols[alph_index];
  return (typeof sym.name === "string" ? sym.name : sym.symbol);
};
// return the alphabet it is like or null
Alphabet.prototype.get_like = function() {
  "use strict";
  return this.like;
};
// return the index of the complement for the given alphabet index
Alphabet.prototype.get_complement = function(alph_index) {
  var comp_e_sym = this.complement[alph_index];
  if (typeof comp_e_sym === "number") {
    return comp_e_sym;
  } else {
    return -1;
  }
};
// return a string containing the core symbols
Alphabet.prototype.get_symbols = function() {
  "use strict";
  var i, core_symbols;
  core_symbols = "";
  for (i = 0; i < this.ncore; i++) {
    core_symbols += this.symbols[i].symbol;
  }
  return core_symbols;
};
// return if the background was not a uniform generated background
Alphabet.prototype.has_bg = function() {
  "use strict";
  return !this.genbg;
};
// get the background frequency for the index
Alphabet.prototype.get_bg_freq = function(alph_index) {
  "use strict";
  var freq, i, symbols;
  if (alph_index >= 0) {
    if (alph_index < this.ncore) {
      return this.background[alph_index];
    } else if (alph_index < this.symbols.length) {
      freq = 0;
      symbols = this.symbols[alph_index].equals;
      for (i = 0; i < symbols.length; i++) {
        freq += this.background[this.encode2core[symbols.charAt(i)]];
      }
      return freq;
    } 
  }
  throw new Error("The alphabet index is out of range.");
};
// get the colour of the index
Alphabet.prototype.get_colour = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("BAD_ALPHABET_INDEX");
  }
  if (typeof this.symbols[alph_index].colour != "string") {
    return "black";
  }
  return "#" + this.symbols[alph_index].colour;
};
// get the rgb components of the colour at the index
Alphabet.prototype.get_rgb = function(alph_index) {
  "use strict";
  if (alph_index < 0 || alph_index >= this.symbols.length) {
    throw new Error("BAD_ALPHABET_INDEX");
  }
  if (typeof this.symbols[alph_index].colour != "string") {
    return {"red": 0, "green": 0, "blue": 0};
  }
  var colour = this.symbols[alph_index].colour;
  var red = parseInt(colour.substr(0, 2), 16) / 255;
  var green = parseInt(colour.substr(2, 2), 16) / 255;
  var blue = parseInt(colour.substr(4, 2), 16) / 255;
  return {"red": red, "green": green, "blue": blue};
};
// convert a symbol into the index
Alphabet.prototype.get_index = function(letter) {
  "use strict";
  var alph_index;
  alph_index = this.encode[letter];
  if (typeof alph_index === "undefined") {
    return -1;
  }
  return alph_index;
};
// convert a symbol into the list of core indexes that it equals
Alphabet.prototype.get_indexes = function(letter) {
  "use strict";
  var alph_index, comprise_str, i, comprise_list;
  alph_index = this.encode[letter];
  if (typeof alph_index === "undefined") {
    throw new Error("Unknown letter");
  }
  comprise_str = this.symbols[alph_index].equals;
  comprise_list = [];
  if (typeof comprise_str == "string") {
    for (i = 0; i < comprise_str.length; i++) {
      comprise_list.push(this.encode2core[comprise_str.charAt(i)]);
    }
  } else {
    comprise_list.push(alph_index);
  }
  return comprise_list;
};
// check if a symbol is the primary way of representing the symbol in the alphabet
Alphabet.prototype.is_prime_symbol = function(letter) {
  var alph_index;
  alph_index = this.encode[letter];
  if (alph_index == null) return false;
  if (this.is_case_insensitive()) {
    return (this.symbols[alph_index].symbol.toUpperCase() == letter.toUpperCase());
  } else {
    return (this.symbols[alph_index].symbol == letter);
  }
};
// compare 2 alphabets
Alphabet.prototype.equals = function(other) {
  "use strict";
  var i, sym1, sym2;
  // first check that it's actually an alphabet object
  if (!(typeof other === "object" && other != null && other instanceof Alphabet)) {
    return false;
  }
  // second shortcircuit if it's the same object
  if (this === other) return true;
  // compare
  if (this.name !== other.name) return false;
  if (this.ncore !== other.ncore) return false;
  if (this.symbols.length !== other.symbols.length) return false;
  for (i = 0; i < this.symbols.length; i++) {
    sym1 = this.symbols[i];
    sym2 = other.symbols[i];
    if (sym1.symbol !== sym2.symbol) return false;
    if (sym1.aliases !== sym2.aliases) return false;
    if (sym1.name !== sym2.name) return false;
    if (typeof sym1.colour !== typeof sym2.colour || 
        (typeof sym1.colour === "string" && typeof sym2.colour === "string" &&
         parseInt(sym1.colour, 16) != parseInt(sym2.colour, 16))) {
      return false;
    }
    if (sym1.complement !== sym2.complement) return false;
    if (sym1.equals !== sym2.equals) return false;
  }
  return true;
};
Alphabet.prototype.check_core_subset = function(super_alph) {
  var complement_same = true;
  var seen_set = {};
  var sub_i, sub_symbol, super_i, super_symbol;
  for (sub_i = 0; sub_i < this.ncore; sub_i++) {
    sub_symbol = this.symbols[sub_i];
    super_i = super_alph.encode[sub_symbol.symbol]; 
    if (super_i == null) return 0;
    super_symbol = super_alph.symbols[super_i];
    if (seen_set[super_i]) return 0;
    seen_set[super_i] = true;
    // check complement
    if (sub_symbol.complement != null && super_symbol.complement != null) {
      if (super_alph.encode[sub_symbol.complement] != super_alph.encode[super_symbol.complement]) {
        complement_same = false;
      }
    } else if (sub_symbol.complement != null || super_symbol.complement != null) {
      complement_same = false;
    }
  }
  return (complement_same ? 1 : -1);
};
// convert a sequence to its reverse complement
Alphabet.prototype.invcomp_seq = function(seq) {
  "use strict";
  var syms, i, e_sym, comp_e_sym;
  if (!this.has_complement()) throw new Error("Alphabet must be complementable");
  syms = seq.split("");
  for (i = 0; i < syms.length; i++) {
    e_sym = this.encode[syms[i]];
    if (typeof e_sym === "undefined") {
      e_sym = this.ncore; // wildcard
    }
    comp_e_sym = this.complement[e_sym];
    if (typeof comp_e_sym === "undefined") {
      comp_e_sym = e_sym; // not complementable
    }
    syms[i] = this.symbols[comp_e_sym].symbol;
  }
  return syms.reverse().join("");
};
// convert the alphabet to the text version
Alphabet.prototype.as_text = function() {
  "use strict";
  function name_as_text(name) {
    var i, c, out;
    out = "\"";
    for (i = 0; i < name.length; i++) {
      c = name.charAt(i);
      if (c == "\"") {
        out += "\\\"";
      } else if (c == "/") {
        out += "\\/";
      } else if (c == "\\") {
        out += "\\\\";
      } else {
        out += c;
      }
    }
    out += "\"";
    return out;
  }
  function symbol_as_text(sym) {
    var out;
    out = sym.symbol;
    if (typeof sym.name === "string" && sym.name != sym.symbol) {
      out += " " + name_as_text(sym.name);
    }
    if (typeof sym.colour === "string") {
      out += " " + sym.colour;
    }
    return out;
  }
  var out, i, j, c, sym;
  out = "";
  // output core symbols with 2 way complements
  for (i = 0; i < this.ncore; i++) {
    c = this.complement[i];
    if (typeof c === "number" && i < c && this.complement[c] === i) {
      out += symbol_as_text(this.symbols[i]) + " ~ " + symbol_as_text(this.symbols[c]) + "\n";  
    }
  }
  // output core symbols with no complement
  for (i = 0; i < this.ncore; i++) {
    if (typeof this.complement[i] === "undefined") {
      out += symbol_as_text(this.symbols[i]) + "\n";
    }
  }
  // output ambiguous symbols that have comprising characters
  for (i = this.ncore; i < this.symbols.length; i++) {
    if (this.symbols[i].equals.length == 0) break;
    out += symbol_as_text(this.symbols[i]) + " = " + this.symbols[i].equals + "\n";
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " = " + this.symbols[i].equals + "\n";
      }
    }
  }
  // output aliases of core symbols
  for (i = 0; i < this.ncore; i++) {
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " = " + this.symbols[i].symbol + "\n";
      }
    }
  }
  // output gap symbols
  i = this.symbols.length - 1;
  if (this.symbols[i].equals.length == 0) {
    out += symbol_as_text(this.symbols[i]) + " =\n";
    if (typeof this.symbols[i].aliases === "string") {
      for (j = 0; j < this.symbols[i].aliases.length; j++) {
        if (this.symbols[i].aliases.charAt(j) == this.symbols[i].symbol) continue;
        out += this.symbols[i].aliases.charAt(j) + " =\n";
      }
    }
  }
  return out;
};
// output the alphabet as it appears in minimal MEME format
Alphabet.prototype.as_meme = function() {
  "use strict";
  function name_as_text(name) {
    var i, c, out;
    out = "\"";
    for (i = 0; i < name.length; i++) {
      c = name.charAt(i);
      if (c == "\"") {
        out += "\\\"";
      } else if (c == "/") {
        out += "\\/";
      } else if (c == "\\") {
        out += "\\\\";
      } else {
        out += c;
      }
    }
    out += "\"";
    return out;
  }
  if (this.equals(AlphStd.DNA)) {
    return "ALPHABET= ACGT\n";
  } else if (this.equals(AlphStd.PROTEIN)) {
    return "ALPHABET= ACDEFGHIKLMNPQRSTVWY\n";
  } else {
    return "ALPHABET" + 
      (this.name != null ? " " + name_as_text(this.name) : "") + 
      (this.like != null ? " " + this.like + "-LIKE" : "") + "\n" +
      this.as_text() + "END ALPHABET\n";
  }
};

// Returns a table showing all the letters in the alphabet
Alphabet.prototype.as_table = function() {
  "use strict";
  var i, j, row, th, td, aliases, equals, sym;
  var table = document.createElement("table");
  // create the core symbol header
  row = table.insertRow(table.rows.length);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Symbol(s)"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Name"));
  row.appendChild(th);
  th = document.createElement("th");
  if (this.has_complement()) {
    th.appendChild(document.createTextNode("Complement"));
  }
  row.appendChild(th);
  // list the core symbols
  for (i = 0; i < this.ncore; i++) {
    row = table.insertRow(table.rows.length);
    td = document.createElement("td");
    if (this.symbols[i].colour != null) {
      td.style.color = '#' + this.symbols[i].colour;
    }
    td.appendChild(document.createTextNode(this.symbols[i].symbol));
    aliases = this.get_aliases(i);
    if (aliases.length > 0) {
      td.appendChild(document.createTextNode(' ' + aliases.split('').join(' ')));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].name != null) {
      td.appendChild(document.createTextNode(this.symbols[i].name));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].complement != null) {
      td.style.color = this.get_colour(this.get_index(this.symbols[i].complement));
      td.appendChild(document.createTextNode(this.symbols[i].complement));
    }
    row.appendChild(td);
  }
  // create the ambiguous symbol header
  row = table.insertRow(table.rows.length);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Symbol(s)"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Name"));
  row.appendChild(th);
  th = document.createElement("th");
  th.appendChild(document.createTextNode("Matches"));
  row.appendChild(th);
  // list the ambiguous symbols
  for (i = this.ncore; i < this.symbols.length; i++) {
    row = table.insertRow(table.rows.length);
    td = document.createElement("td");
    if (this.symbols[i].colour != null) {
      td.style.color = '#' + this.symbols[i].colour;
    }
    td.appendChild(document.createTextNode(this.symbols[i].symbol));
    aliases = this.get_aliases(i);
    if (aliases.length > 0) {
      td.appendChild(document.createTextNode(' ' + aliases.split('').join(' ')));
    }
    row.appendChild(td);
    td = document.createElement("td");
    if (this.symbols[i].name != null) {
      td.appendChild(document.createTextNode(this.symbols[i].name));
    }
    row.appendChild(td);
    td = document.createElement("td");
    equals = this.symbols[i].equals.split('');
    for (j = 0; j < equals.length; j++) {
      if (j != 0) td.appendChild(document.createTextNode(' '));
      sym = document.createElement("span");
      sym.style.color = this.get_colour(this.get_index(equals[j]));
      sym.appendChild(document.createTextNode(equals[j]));
      td.appendChild(sym);
    }
    row.appendChild(td);
  }
  return table;
};

// returns a dictionary of the colours for EPS
Alphabet.prototype._as_eps_dict = function() {
  "use strict";
  var i, sym, rgb;
  var out = "/fullColourDict <<\n";
  for (i = 0; i < this.ncore; i++) {
    sym = this.get_symbol(i);
    sym = sym.replace(/\\/g, "\\\\");
    sym = sym.replace(/\(/g, "\\(");
    sym = sym.replace(/\)/g, "\\)");
    rgb = this.get_rgb(i);
    out += " (" + sym + ") [" + rgb.red.toFixed(4) + " " + rgb.green.toFixed(4) + " " + rgb.blue.toFixed(4) + "]\n";
  }
  out += ">> def\n";
  out += "/mutedColourDict <<\n";
  for (i = 0; i < this.ncore; i++) {
    sym = this.get_symbol(i);
    sym = sym.replace(/\\/g, "\\\\");
    sym = sym.replace(/\(/g, "\\(");
    sym = sym.replace(/\)/g, "\\)");
    rgb = Alphabet.lighten_colour(this.get_rgb(i));
    out += " (" + sym + ") [" + rgb.red.toFixed(4) + " " + rgb.green.toFixed(4) + " " + rgb.blue.toFixed(4) + "]\n";
  }
  out += ">> def\n";
  return out;
};

// return the alphabet name or a list of primary symbols
Alphabet.prototype.toString = function() {
  "use strict";
  if (this.name != null) {
    return this.name;
  } else {
    return this.get_symbols();
  }
};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Helper functions
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Convert a colour specified in RGB colourspace values into LAB colourspace
Alphabet.rgb2lab = function(rgb) {
  "use strict";
  var xyzHelper, labHelper;
  // XYZ helper
  xyzHelper = function(value) {
    if (value > 0.0445) {
      value = (value + 0.055) / 1.055;
      value = Math.pow(value, 2.4);
    } else {
      value /= 12.92;
    }
    value *= 100;
    return value;
  };
  // lab helper
  labHelper = function(value) {
    if (value > 0.008856) {
      value = Math.pow(value, 1.0 / 3.0);
    } else {
      value = (7.787 * value) + (16.0 / 116.0);
    }
    return value;
  };
  // convert into XYZ colourspace
  var c1, c2, c3;
  if (typeof rgb == "number") {
    c1 = xyzHelper(((rgb >> 16) & 0xFF) / 255.0);
    c2 = xyzHelper(((rgb >> 8) & 0xFF) / 255.0);
    c3 = xyzHelper((rgb & 0xFF) / 255.0);
  } else {
    c1 = xyzHelper(rgb.red);
    c2 = xyzHelper(rgb.green);
    c3 = xyzHelper(rgb.blue);
  }
  var x = (c1 * 0.4124) + (c2 * 0.3576) + (c3 * 0.1805);
  var y = (c1 * 0.2126) + (c2 * 0.7152) + (c3 * 0.0722);
  var z = (c1 * 0.0193) + (c2 * 0.1192) + (c3 * 0.9505);
  // convert into Lab colourspace
  c1 = labHelper(x / 95.047);
  c2 = labHelper(y / 100.0);
  c3 = labHelper(z / 108.883);
  var l = (116.0 * c2) - 16;
  var a = 500.0 * (c1 - c2);
  var b = 200.0 * (c2 - c3);
  return {"l": l, "a": a, "b": b};
};

// Convert a colour specified in HSV colourspace into RGB colourspace
Alphabet.hsv2rgb = function(hue, sat, value, output_object) {
  // achromatic (grey)
  var r = value;
  var g = value;
  var b = value;
  if (sat != 0) {
    var h = hue / 60.0;
    var i = Math.floor(h);
    var f = h - i;
    var p = value * (1.0 - sat);
    var q = value * (1.0 - (sat * f));
    var t = value * (1.0 - (sat * (1.0 - f)));
    if (i == 0) {
      r = value;
      g = t;
      b = p;
    } else if (i == 1) {
      r = q;
      g = value;
      b = p;
    } else if (i == 2) {
      r = p;
      g = value;
      b = t;
    } else if (i == 3) {
      r = p;
      g = q;
      b = value;
    } else if (i == 4) {
      r = t;
      g = p;
      b = value;
    } else {
      r = value;
      g = p;
      b = q;
    }
  }
  if (output_object) {
    return {"red": r, "green": g, "blue": b};
  } else {
    return (Math.floor(r * 255) << 15) | (Math.floor(g * 255) << 8) | (Math.floor(b * 255));
  }
};

// Calculate a distance score between two colours in LAB colourspace
Alphabet.lab_dist = function(lab1, lab2) {
  var c1 = Math.sqrt((lab1.l * lab1.l) + (lab1.a * lab1.a));
  var c2 = Math.sqrt((lab2.l * lab2.l) + (lab2.a * lab2.a));
  var dc = c1 - c2;
  var dl = lab1.l - lab2.l;
  var da = lab1.a - lab2.a;
  var db = lab1.b - lab2.b;
  // we don't want NaN due to rounding errors so fudge things a bit...
  var dh = 0;
  var dh_squared = (da * da) + (db * db) - (dc * dc);
  if (dh_squared > 0) {
    dh = Math.sqrt(dh_squared);
  }
  var first = dl;
  var second = dc / (1.0 + (0.045 * c1));
  var third = dh / (1.0 + (0.015 * c1));
  return Math.sqrt((first * first) + (second * second) + (third * third));
};

// convert an RGB value into a HSL value
Alphabet.rgb2hsl = function(rgb) {
  "use strict";
  var min, max, delta, h, s, l, r, g, b;
  if (typeof rgb == "number") {
    r = ((rgb >> 16) & 0xFF) / 255.0;
    g = ((rgb >> 8) & 0xFF) / 255.0;
    b = (rgb & 0xFF) / 255.0;
  } else {
    r = rgb.red;
    g = rgb.green;
    b = rgb.blue;
  }
  min = Math.min(r, g, b);
  max = Math.max(r, g, b);
  delta = max - min;
  l = min + (delta / 2);
  if (max == min) {
    h = 0; // achromatic (grayscale)
    s = 0;
  } else {
    if (l > 0.5) {
      s = delta / (2 - max - min);
    } else {
      s = delta / (max + min);
    }
    if (max == r) {
      h = (g - b) / delta;
      if (g < b) h += 6;
    } else if (max == g) {
      h = ((b - r) / delta) + 2;
    } else {
      h = ((r - g) / delta) + 4;
    }
    h /= 6;
  }
  return {"h": h, "s": s, "l": l};
};

// convert a HSL value into an RGB value
Alphabet.hsl2rgb = function(hsl, output_object) {
  "use strict";
  function _hue(p, q, t) {
    "use strict";
    if (t < 0) t += 1;
    else if (t > 1) t -= 1;
    if (t < (1.0 / 6.0)) {
      return p + ((q - p) * 6.0 * t);
    } else if (t < 0.5) {
      return q;
    } else if (t < (2.0 / 3.0)) {
      return p + ((q - p) * ((2.0 / 3.0) - t) * 6.0);
    } else {
      return p;
    }
  }
  var r, g, b, p, q;
  if (hsl.s == 0) {
    // achromatic (grayscale)
    r = hsl.l;
    g = hsl.l;
    b = hsl.l;
  } else {
    if (hsl.l < 0.5) {
      q = hsl.l * (1 + hsl.s);
    } else {
      q = hsl.l + hsl.s - (hsl.l * hsl.s);
    }
    p = (2 * hsl.l) - q;
    r = _hue(p, q, hsl.h + (1.0 / 3.0));
    g = _hue(p, q, hsl.h);
    b = _hue(p, q, hsl.h - (1.0 / 3.0));
  }
  if (output_object) {
    return {"red": r, "green": g, "blue": b};
  } else {
    return (Math.floor(r * 255) << 15) | (Math.floor(g * 255) << 8) | (Math.floor(b * 255));
  }
};

Alphabet.lighten_colour = function(rgb) {
  "use strict";
  var hsl = Alphabet.rgb2hsl(rgb);
  hsl.l += (1.0 - hsl.l) * 2 / 3;
  return Alphabet.hsl2rgb(hsl, typeof rgb != "number");
};

//======================================================================
// end Alphabet object
//======================================================================

//======================================================================
// start StandardAlphabet object
//======================================================================

// an extension of the alphabet object to support some additional fields 
// only present in standard alphabets.
var StandardAlphabet = function(enum_code, enum_name, alphabet_data) {
  Alphabet.apply(this, [alphabet_data]);
  this.enum_code = enum_code;
  this.enum_name = enum_name;
};
StandardAlphabet.prototype = Alphabet.prototype;
StandardAlphabet.prototype.constructor = StandardAlphabet;

// A unique code for this standard alphabet.
// This code will be a power of 2 to enable creation of bitsets for
// a selection of standard alphabets.
StandardAlphabet.prototype.get_code = function() {
  return this.enum_code;
};

// A unique name for this standard alphabet.
// this name will be all upper case and the same as the property that
// refers to this alphabet in the AlphStd collection.
StandardAlphabet.prototype.get_enum = function() {
  return this.enum_name;
};

//======================================================================
// end StandardAlphabet object
//======================================================================

// A collection of standard alphabets.
var AlphStd = {
  RNA: new StandardAlphabet(1, "RNA", {
    "name": "RNA",
    "like": "RNA",
    "ncore": 4,
    "symbols": [
      {"symbol": "A", "name": "Adenine", "colour": "CC0000"},
      {"symbol": "C", "name": "Cytosine", "colour": "0000CC"},
      {"symbol": "G", "name": "Guanine", "colour": "FFB300"},
      {"symbol": "U", "name": "Uracil", "colour": "008000",
        "aliases": "T"},
      {"symbol": "N", "name": "Any base", "equals": "ACGU", "aliases": "X."},
      {"symbol": "V", "name": "Not U", "equals": "ACG"},
      {"symbol": "H", "name": "Not G", "equals": "ACU"},
      {"symbol": "D", "name": "Not C", "equals": "AGU"},
      {"symbol": "B", "name": "Not A", "equals": "CGU"},
      {"symbol": "M", "name": "Amino", "equals": "AC"},
      {"symbol": "R", "name": "Purine", "equals": "AG"},
      {"symbol": "W", "name": "Weak", "equals": "AU"}, 
      {"symbol": "S", "name": "Strong", "equals": "CG"},
      {"symbol": "Y", "name": "Pyrimidine", "equals": "CU"},
      {"symbol": "K", "name": "Keto", "equals": "GU"}
    ]
  }), 
  DNA: new StandardAlphabet(2, "DNA", {
    "name": "DNA",
    "like": "DNA",
    "ncore": 4,
    "symbols": [
      {"symbol": "A", "name": "Adenine", "colour": "CC0000", "complement": "T"},
      {"symbol": "C", "name": "Cytosine", "colour": "0000CC", "complement": "G"},
      {"symbol": "G", "name": "Guanine", "colour": "FFB300", "complement": "C"},
      {"symbol": "T", "name": "Thymine", "colour": "008000", "complement": "A",
        "aliases": "U"},
      {"symbol": "N", "name": "Any base", "equals": "ACGT", "aliases": "X."},
      {"symbol": "V", "name": "Not T", "equals": "ACG"},
      {"symbol": "H", "name": "Not G", "equals": "ACT"},
      {"symbol": "D", "name": "Not C", "equals": "AGT"},
      {"symbol": "B", "name": "Not A", "equals": "CGT"},
      {"symbol": "M", "name": "Amino", "equals": "AC"},
      {"symbol": "R", "name": "Purine", "equals": "AG"},
      {"symbol": "W", "name": "Weak", "equals": "AT"}, 
      {"symbol": "S", "name": "Strong", "equals": "CG"},
      {"symbol": "Y", "name": "Pyrimidine", "equals": "CT"},
      {"symbol": "K", "name": "Keto", "equals": "GT"}
    ]
  }), 
  PROTEIN: new StandardAlphabet(4, "PROTEIN", {
    "name": "Protein",
    "like": "PROTEIN",
    "ncore": 20,
    "symbols": [
      {"symbol": "A", "name": "Alanine", "colour": "0000CC"},
      {"symbol": "C", "name": "Cysteine", "colour": "0000CC"},
      {"symbol": "D", "name": "Aspartic acid", "colour": "FF00FF"},
      {"symbol": "E", "name": "Glutamic acid", "colour": "FF00FF"},
      {"symbol": "F", "name": "Phenylalanine", "colour": "0000CC"},
      {"symbol": "G", "name": "Glycine", "colour": "FFB300"},
      {"symbol": "H", "name": "Histidine", "colour": "FFCCCC"},
      {"symbol": "I", "name": "Isoleucine", "colour": "0000CC"},
      {"symbol": "K", "name": "Lysine", "colour": "CC0000"},
      {"symbol": "L", "name": "Leucine", "colour": "0000CC"},
      {"symbol": "M", "name": "Methionine", "colour": "0000CC"},
      {"symbol": "N", "name": "Asparagine", "colour": "008000"},
      {"symbol": "P", "name": "Proline", "colour": "FFFF00"},
      {"symbol": "Q", "name": "Glutamine", "colour": "008000"},
      {"symbol": "R", "name": "Arginine", "colour": "CC0000"},
      {"symbol": "S", "name": "Serine", "colour": "008000"},
      {"symbol": "T", "name": "Threonine", "colour": "008000"},
      {"symbol": "V", "name": "Valine", "colour": "0000CC"},
      {"symbol": "W", "name": "Tryptophan", "colour": "0000CC"},
      {"symbol": "Y", "name": "Tyrosine", "colour": "33E6CC"},
      {"symbol": "X", "name": "Any amino acid", "equals": "ACDEFGHIKLMNPQRSTVWY", "aliases": "*."},
      {"symbol": "B", "name": "Asparagine or Aspartic acid", "equals": "DN"}, 
      {"symbol": "Z", "name": "Glutamine or Glutamic acid", "equals": "EQ"}, 
      {"symbol": "J", "name": "Leucine or Isoleucine", "equals": "IL"}
    ]
  })
};

//======================================================================
// start Symbol object
//======================================================================
var Symbol = function(alph_index, scale, alphabet) {
  "use strict";
  //variable prototype
  this.symbol = alphabet.get_symbol(alph_index);
  this.scale = scale;
  this.colour = alphabet.get_colour(alph_index);
};

Symbol.prototype.get_symbol = function() {
  "use strict";
  return this.symbol;
};

Symbol.prototype.get_scale = function() {
  "use strict";
  return this.scale;
};

Symbol.prototype.get_colour = function() {
  "use strict";
  return this.colour;
};

Symbol.prototype.toString = function() {
  "use strict";
  return this.symbol + " " + (Math.round(this.scale*1000)/10) + "%";
};

function compare_symbol(sym1, sym2) {
  "use strict";
  if (sym1.get_scale() < sym2.get_scale()) {
    return -1;
  } else if (sym1.get_scale() > sym2.get_scale()) {
    return 1;
  } else {
    return 0;
  }
}
//======================================================================
// end Symbol object
//======================================================================

//======================================================================
// start Pspm object
//======================================================================
var Pspm = function(matrix, name, ltrim, rtrim, nsites, evalue, pssm, alt) {
  "use strict";
  var row, col, data, row_sum, delta, evalue_re;
  if (typeof name !== "string") {
    name = "";
  }
  this.name = name;
  //construct
  if (matrix instanceof Pspm) {
    // copy constructor
    this.alph_length = matrix.alph_length;
    this.motif_length = matrix.motif_length;
    this.name = matrix.name;
    this.alt = matrix.alt;
    this.nsites = matrix.nsites;
    this.evalue = matrix.evalue;
    this.ltrim = matrix.ltrim;
    this.rtrim = matrix.rtrim;
    this.pspm = [];
    for (row = 0; row < matrix.motif_length; row++) {
      this.pspm[row] = [];
      for (col = 0; col < matrix.alph_length; col++) {
        this.pspm[row][col] = matrix.pspm[row][col];
      }
    }
    if (matrix.pssm != null) {
      this.pssm = [];
      for (row = 0; row < matrix.motif_length; row++) {
        this.pspm[row] = [];
        for (col = 0; col < matrix.alph_length; col++) {
          this.pssm[row][col] = matrix.pssm[row][col];
        }
      }
    }
  } else {
    // check parameters
    if (ltrim == null) {
      ltrim = 0;
    } else if (typeof ltrim !== "number" || ltrim % 1 !== 0 || ltrim < 0) {
      throw new Error("ltrim must be a non-negative integer, got: " + ltrim);
    }
    if (rtrim == null) {
      rtrim = 0;
    } else if (typeof rtrim !== "number" || rtrim % 1 !== 0 || rtrim < 0) {
      throw new Error("rtrim must be a non-negative integer, got: " + rtrim);
    }
    if (nsites != null) {
      if (typeof nsites !== "number" || nsites < 0) {
        throw new Error("nsites must be a positive number, got: " + nsites);
      } else if (nsites == 0) {
        nsites = null;
      }
    }
    if (evalue != null) {
      if (typeof evalue === "number") {
        if (evalue < 0) {
          throw new Error("evalue must be a non-negative number, got: " + evalue);
        }
      } else if (typeof evalue === "string") {
        evalue_re = /^((?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)|inf)$/;
        if (!evalue_re.test(evalue)) {
          throw new Error("evalue must be a non-negative number, got: " + evalue);
        }
      } else {
        throw new Error("evalue must be a non-negative number, got: " + evalue);
      }
    }
    // set properties
    this.name = name;
    this.alt = alt;
    this.nsites = nsites;
    this.evalue = evalue;
    this.ltrim = ltrim;
    this.rtrim = rtrim;
    if (typeof matrix === "string") {
      // string constructor
      data = parse_pspm_string(matrix);
      this.alph_length = data["alph_length"];
      this.motif_length = data["motif_length"];
      this.pspm = data["pspm"];
      if (this.evalue == null) {
        if (data["evalue"] != null) {
          this.evalue = data["evalue"];
        } else {
          this.evalue = 0;
        }
      }
      if (this.nsites == null) {
        if (typeof data["nsites"] === "number") {
          this.nsites = data["nsites"];
        } else {
          this.nsites = 20;
        }
      }
    } else {
      // assume pspm is a nested array
      this.motif_length = matrix.length;
      this.alph_length = (matrix.length > 0 ? matrix[0].length : 0);
      if (this.nsites == null) {
        this.nsites = 20;
      }
      if (this.evalue == null) {
        this.evalue = 0;
      }
      this.pspm = [];
      // copy pspm and check
      for (row = 0; row < this.motif_length; row++) {
        if (this.alph_length != matrix[row].length) {
          throw new Error("COLUMN_MISMATCH");
        }
        this.pspm[row] = [];
        row_sum = 0;
        for (col = 0; col < this.alph_length; col++) {
          this.pspm[row][col] = matrix[row][col];
          row_sum += this.pspm[row][col];
        }
        delta = 0.1;
        if (isNaN(row_sum) || (row_sum > 1 && (row_sum - 1) > delta) || 
            (row_sum < 1 && (1 - row_sum) > delta)) {
          throw new Error("INVALID_SUM");
        }
      }
      // copy pssm
      if (pssm != null) {
        this.pssm = [];
        for (row = 0; row < this.motif_length; row++) {
          this.pssm[row] = [];
          for (col = 0; col < this.alph_length; col++) {
            this.pssm[row][col] = pssm[row][col];
          }
        }
      }
    }
  }
};

Pspm.prototype.copy = function() {
  "use strict";
  return new Pspm(this);
};

Pspm.prototype.reverse = function() {
  "use strict";
  var x, y, temp, temp_trim;
  //reverse
  x = 0;
  y = this.motif_length-1;
  while (x < y) {
    temp = this.pspm[x];
    this.pspm[x] = this.pspm[y];
    this.pspm[y] = temp;
    x++;
    y--;
  }
  // reverse pssm (if defined)
  if (typeof this.pssm !== "undefined") {
    //reverse
    x = 0;
    y = this.motif_length-1;
    while (x < y) {
      temp = this.pssm[x];
      this.pspm[x] = this.pssm[y];
      this.pssm[y] = temp;
      x++;
      y--;
    }
  }
  //swap triming
  temp_trim = this.ltrim;
  this.ltrim = this.rtrim;
  this.rtrim = temp_trim;
  return this; //allow function chaining...
};

Pspm.prototype.reverse_complement = function(alphabet) {
  "use strict";
  var x, y, temp, i, row, c, temp_trim;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size of the pspm.");
  }
  if (!alphabet.has_complement()) {
    throw new Error("The specified alphabet can not be complemented.");
  }
  // reverse motif
  this.reverse();
  //complement
  for (x = 0; x < this.motif_length; x++) {
    row = this.pspm[x];
    for (i = 0; i < row.length; i++) {
      c = alphabet.get_complement(i);
      if (c < i) continue;
      temp = row[i];
      row[i] = row[c];
      row[c] = temp;
    }
  }
  // complement pssm (if defined)
  if (typeof this.pssm !== "undefined") {
    //complement
    for (x = 0; x < this.motif_length; x++) {
      row = this.pssm[x];
      for (i = 0; i < row.length; i++) {
        c = alphabet.get_complement(i);
        if (c < i) continue;
        temp = row[i];
        row[i] = row[c];
        row[c] = temp;
      }
    }
  }
  return this; //allow function chaining...
};

Pspm.prototype.get_stack = function(position, alphabet, ssc) {
  "use strict";
  var row, stack_ic, alphabet_ic, stack, i, sym;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size of the pspm.");
  }
  row = this.pspm[position];
  stack_ic = this.get_stack_ic(position, alphabet);
  if (ssc) stack_ic -= this.get_error(alphabet);
  alphabet_ic = alphabet.get_ic();
  stack = [];
  for (i = 0; i < this.alph_length; i++) {
    sym = new Symbol(i, row[i]*stack_ic/alphabet_ic, alphabet);
    if (sym.get_scale() <= 0) {
      continue;
    }
    stack.push(sym);
  }
  stack.sort(compare_symbol);
  return stack;
};

Pspm.prototype.get_stack_ic = function(position, alphabet) {
  "use strict";
  var row, H, i;
  if (this.alph_length != alphabet.get_size_core()) {
    throw new Error("The alphabet size does not match the size fo the pspm.");
  }
  row = this.pspm[position];
  H = 0;
  for (i = 0; i < this.alph_length; i++) {
    if (row[i] === 0) {
      continue;
    }
    H -= (row[i] * (Math.log(row[i]) / Math.LN2));
  }
  return alphabet.get_ic() - H;
};

Pspm.prototype.get_error = function(alphabet) {
  "use strict";
  if (this.nsites === 0) {
    return 0;
  }
  return (alphabet.get_size_core()-1) / (2 * Math.LN2 * this.nsites);
};

Pspm.prototype.get_motif_length = function() {
  "use strict";
  return this.motif_length;
};

Pspm.prototype.get_alph_length = function() {
  "use strict";
  return this.alph_length;
};

Pspm.prototype.get_left_trim = function() {
  "use strict";
  return this.ltrim;
};

Pspm.prototype.get_right_trim = function() {
  "use strict";
  return this.rtrim;
};

Pspm.prototype.as_best_match = function(alphabet) {
  "use strict";
  var match, odds, best_odds, best_index;
  var i, j;
  match = "";
  for (i = 0; i < this.motif_length; i++) {
    best_index = 0;
    best_odds = this.pspm[i][0] / alphabet.get_bg_freq(0);
    for (j = 1; j < this.alph_length; j++) {
      odds = this.pspm[i][j] / alphabet.get_bg_freq(j);
      if (odds > best_odds) {
        best_odds = odds;
        best_index = j;
      }
    }
    match += alphabet.get_symbol(best_index);
  }
  return match;
};

Pspm.prototype.as_count_matrix = function() {
  "use strict";
  var count, count_text, text;
  var i, j;
  text = "";
  for (i = 0; i < this.motif_length; i++) {
    if (i !== 0) {
      text += "\n";
    }
    for (j = 0; j < this.alph_length; j++) {
      if (j !== 0) {
        text += " ";
      }
      count = Math.round(this.nsites * this.pspm[i][j]);
      count_text = "" + count;
      // pad up to length of 4
      if (count_text.length < 4) {
        text += (new Array(5 - count_text.length)).join(" ") + count_text;
      } else {
        text += count_text;
      }
    }
  }
  return text; 
};

Pspm.prototype.as_probability_matrix = function() {
  "use strict";
  var text;
  var i, j;
  text = "";
  for (i = 0; i < this.motif_length; i++) {
    if (i !== 0) {
      text += "\n";
    }
    for (j = 0; j < this.alph_length; j++) {
      if (j !== 0) {
        text += " ";
      }
      text += this.pspm[i][j].toFixed(6);
    }
  }
  return text; 
};

Pspm.prototype.as_score_matrix = function(alphabet, pseudo) {
  "use strict";
  var me, score, out, row, col, score_text;
  me = this;
  if (typeof this.pssm === "undefined") {
    if (!(typeof alphabet === "object" && alphabet != null && alphabet instanceof Alphabet)) {
      throw new Error("The alphabet is required to generate the pssm.");
    }
    if (typeof pseudo === "undefined") {
      pseudo = 0.01;
    } else if (typeof pseudo !== "number" || pseudo < 0) {
      throw new Error("Expected positive number for pseudocount");
    }
    score = function(row, col) {
      "use strict";
      var p, bg, p2;
      p = me.pspm[row][col];
      bg = alphabet.get_bg_freq(col);
      p2 = (p * me.nsites + bg * pseudo) / (me.nsites + pseudo);
      return (p2 > 0 ? Math.round((Math.log(p2 / bg) / Math.LN2) * 100) : -10000);
    };
  } else {
    score = function(row, col) {
      "use strict";
      return me.pssm[row][col];
    };
  }
  out = "";
  for (row = 0; row < this.motif_length; row++) {
    for (col = 0; col < this.alph_length; col++) {
      if (col !== 0) {
        out += " ";
      }
      score_text = "" + score(row, col);
      // pad out to 6 characters
      if (score_text.length < 6) {
        out += (new Array(7 - score_text.length)).join(" ") + score_text;
      } else {
        out += score_text;
      }
    }
    out += "\n";
  }
  return out;
}

Pspm.prototype.as_pspm = function() {
  "use strict";
  return "letter-probability matrix: alength= " + this.alph_length + 
      " w= " + this.motif_length + " nsites= " + this.nsites + 
      " E= " + (typeof this.evalue === "number" ? 
          this.evalue.toExponential() : this.evalue) + "\n" +
      this.as_probability_matrix();
};

Pspm.prototype.as_pssm = function(alphabet, pseudo) {
  "use strict";
  return "log-odds matrix: alength= " + this.alph_length + 
      " w= " + this.motif_length + 
      " E= " + (typeof this.evalue == "number" ?
          this.evalue.toExponential() : this.evalue) + "\n" +
      this.as_score_matrix(alphabet, pseudo);
};

Pspm.prototype.as_meme = function(options) {
  var with_header, with_pspm, with_pssm, version, alphabet, bg_source, pseudocount, strands;
  var out, alen, i;
  // get the options
  if (typeof options !== "object" || options === null) {
    options = {};
  }
  with_header = (typeof options["with_header"] === "boolean" ? options["with_header"] : false);
  with_pspm = (typeof options["with_pspm"] === "boolean" ? options["with_pspm"] : false);
  with_pssm = (typeof options["with_pssm"] === "boolean" ? options["with_pssm"] : false);
  if (!with_pspm && !with_pssm) with_pspm = true;
  if (with_header) {
    if (typeof options["version"] === "string" && /^\d+(?:\.\d+){0,2}$/.test(options["version"])) {
      version = options["version"];
    } else if (typeof options["version"] === "number") {
      version = options["version"].toFixed(0);
    } else {
      version = "4";
    }
    if (typeof options["strands"] === "number" && options["strands"] === 1) {
      strands = 1;
    } else {
      strands = 2;
    }
    if (typeof options["bg_source"] === "string") {
      bg_source = options["bg_source"];
    } else {
      bg_source = "unknown source";
    }
    if (typeof options["alphabet"] === "object" && options["alphabet"] != null
        && options["alphabet"] instanceof Alphabet) {
      alphabet = options["alphabet"];
    } else {
      throw new Error("The alphabet is required to generate the header.");
    }
  }
  // now create the output
  out = "";
  if (with_header) {
    out = "MEME version " + version + "\n\n";
    out += alphabet.as_meme() + "\n";
    if (alphabet.has_complement()) { // assume DNA has both strands unless otherwise specified
      out += "strands: " + (strands === 1 ? "+" : "+ -") + "\n\n";
    }
    out += "Background letter frequencies (from " + bg_source + "):\n";
    alen = alphabet.get_size_core();
    for (i = 0; i < alen; i++) {
      if (i !== 0) {
        if (i % 9 === 0) { // maximum of nine entries per line
          out += "\n";
        } else {
          out += " ";
        }
      }
      out += alphabet.get_symbol(i) + " " + alphabet.get_bg_freq(i).toFixed(3);
    }
  }
  out += "\n\n";
  out += "MOTIF " + this.name + (this.alt == null ? "" : " " + this.alt);
  if (with_pssm) {
    out += "\n\n";
    out += this.as_pssm(options["alphabet"], options["pseudocount"]);
  }
  if (with_pspm) {
    out += "\n\n";
    out += this.as_pspm();
  }
  return out;
}

Pspm.prototype.toString = function() {
  "use strict";
  var str, i, row;
  str = "";
  for (i = 0; i < this.pspm.length; i++) {
    row = this.pspm[i];
    str += row.join("\t") + "\n";
  }
  return str;
};

function parse_pspm_properties(str) {
  "use strict";
  var parts, i, eqpos, before, after, properties, prop, num, num_re;
  num_re = /^((?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)|inf)$/;
  parts = trim(str).split(/\s+/);
  // split up words containing =
  for (i = 0; i < parts.length;) {
    eqpos = parts[i].indexOf("=");
    if (eqpos != -1) {
      before = parts[i].substr(0, eqpos);
      after = parts[i].substr(eqpos+1);
      if (before.length > 0 && after.length > 0) {
        parts.splice(i, 1, before, "=", after);
        i += 3;
      } else if (before.length > 0) {
        parts.splice(i, 1, before, "=");
        i += 2;
      } else if (after.length > 0) {
        parts.splice(i, 1, "=", after);
        i += 2;
      } else {
        parts.splice(i, 1, "=");
        i++;
      }
    } else {
      i++;
    }
  }
  properties = {};
  for (i = 0; i < parts.length; i += 3) {
    if (parts.length - i < 3) {
      throw new Error("Expected PSPM property was incomplete. "+
          "Remaing parts are: " + parts.slice(i).join(" "));
    }
    if (parts[i+1] !== "=") {
      throw new Error("Expected '=' in PSPM property between key and " +
          "value but got " + parts[i+1]); 
    }
    prop = parts[i].toLowerCase();
    num = parts[i+2];
    if (!num_re.test(num)) {
      throw new Error("Expected numeric value for PSPM property '" + 
          prop + "' but got '" + num + "'");
    }
    properties[prop] = num;
  }
  return properties;
}

function parse_pspm_string(pspm_string) {
  "use strict";
  var header_re, lines, first_line, line_num, col_num, alph_length, 
      motif_length, nsites, evalue, pspm, i, line, match, props, parts,
      j, prob;
  header_re = /^letter-probability\s+matrix:(.*)$/i;
  lines = pspm_string.split(/\n/);
  first_line = true;
  line_num = 0;
  col_num = 0;
  alph_length;
  motif_length;
  nsites;
  evalue;
  pspm = [];
  for (i = 0; i < lines.length; i++) {
    line = trim(lines[i]);
    if (line.length === 0) { 
      continue;
    }
    // check the first line for a header though allow matrices without it
    if (first_line) {
      first_line = false;
      match = header_re.exec(line);
      if (match !== null) {
        props = parse_pspm_properties(match[1]);
        if (props.hasOwnProperty("alength")) {
          alph_length = parseFloat(props["alength"]);
          if (alph_length != 4 && alph_length != 20) {
            throw new Error("PSPM property alength should be 4 or 20" +
                " but got " + alph_length);
          }
        }
        if (props.hasOwnProperty("w")) {
          motif_length = parseFloat(props["w"]);
          if (motif_length % 1 !== 0 || motif_length < 1) {
            throw new Error("PSPM property w should be an integer larger " +
                "than zero but got " + motif_length);
          }
        }
        if (props.hasOwnProperty("nsites")) {
          nsites = parseFloat(props["nsites"]);
          if (nsites <= 0) {
            throw new Error("PSPM property nsites should be larger than " +
                "zero but got " + nsites);
          }
        }
        if (props.hasOwnProperty("e")) {
          evalue = props["e"];
          if (evalue < 0) {
            throw new Error("PSPM property evalue should be " +
                "non-negative but got " + evalue);
          }
        }
        continue;
      }
    }
    pspm[line_num] = [];
    col_num = 0;
    parts = line.split(/\s+/);
    for (j = 0; j < parts.length; j++) {
      prob = parseFloat(parts[j]);
      if (prob != parts[j] || prob < 0 || prob > 1) {
        throw new Error("Expected probability but got '" + parts[j] + "'"); 
      }
      pspm[line_num][col_num] = prob;
      col_num++;
    }
    line_num++;
  }
  if (typeof motif_length === "number") {
    if (pspm.length != motif_length) {
      throw new Error("Expected PSPM to have a motif length of " + 
          motif_length + " but it was actually " + pspm.length);
    }
  } else {
    motif_length = pspm.length;
  }
  if (typeof alph_length !== "number") {
    alph_length = pspm[0].length;
    if (alph_length != 4 && alph_length != 20) {
      throw new Error("Expected length of first row in the PSPM to be " +
          "either 4 or 20 but got " + alph_length);
    }
  }
  for (i = 0; i < pspm.length; i++) {
    if (pspm[i].length != alph_length) {
      throw new Error("Expected PSPM row " + i + " to have a length of " + 
          alph_length + " but the length was " + pspm[i].length);
    }
  }
  return {"pspm": pspm, "motif_length": motif_length, 
    "alph_length": alph_length, "nsites": nsites, "evalue": evalue};
}
//======================================================================
// end Pspm object
//======================================================================

//======================================================================
// start Logo object
//======================================================================

var Logo = function(alphabet, options) {
  "use strict";
  this.alphabet = alphabet;
  this.fine_text = "";
  this.x_axis = 1;
  this.y_axis = true;
  this.xlate_nsyms = 1;
  this.xlate_start = null;
  this.xlate_end = null;
  this.pspm_list = [];
  this.pspm_column = [];
  this.rows = 0;
  this.columns = 0;
  if (typeof options === "string") {
    // the old method signature had fine_text here so we support that
    this.fine_text = options;
  } else if (typeof options === "object" && options != null) {
    this.fine_text = (typeof options.fine_text === "string" ? options.fine_text : "");
    this.x_axis = (typeof options.x_axis === "boolean" ? (options.x_axis ? 1 : 0) : 1);
    if (options.x_axis_hidden != null && options.x_axis_hidden) this.x_axis = -1;
    this.y_axis = (typeof options.y_axis === "boolean" ? options.y_axis : true);
    this.xlate_nsyms = (typeof options.xlate_nsyms === "number" ? options.xlate_nsyms : this.xlate_nsyms);
    this.xlate_start = (typeof options.xlate_start === "number" ? options.xlate_start : this.xlate_start);
    this.xlate_end = (typeof options.xlate_end === "number" ? options.xlate_end : this.xlate_end);
  }
};

Logo.prototype.add_pspm = function(pspm, column) {
  "use strict";
  var col;
  if (typeof column === "undefined") {
    column = 0;
  } else if (column < 0) {
    throw new Error("Column index out of bounds.");
  }
  this.pspm_list[this.rows] = pspm;
  this.pspm_column[this.rows] = column;
  this.rows++;
  col = column + pspm.get_motif_length();
  if (col > this.columns) {
    this.columns = col;
  }
};

Logo.prototype.get_columns = function() {
  "use strict";
  return this.columns;
};

Logo.prototype.get_xlate_nsyms = function() {
  "use strict";
  return this.xlate_nsyms;
};

Logo.prototype.get_xlate_start = function() {
  "use strict";
  return (this.xlate_start != null ? this.xlate_start : 0);
};

Logo.prototype.get_xlate_end = function() {
  "use strict";
  return (this.xlate_end != null ? this.xlate_end : this.columns * this.xlate_nsyms);
};

Logo.prototype.get_xlate_columns = function() {
  "use strict";
  return this.get_xlate_end() - this.get_xlate_start();
};

Logo.prototype.get_rows = function() {
  "use strict";
  return this.rows;
};

Logo.prototype.get_pspm = function(row_index) {
  "use strict";
  if (row_index < 0 || row_index >= this.rows) {
    throw new Error("INDEX_OUT_OF_BOUNDS");
  }
  return this.pspm_list[row_index];
};

Logo.prototype.get_offset = function(row_index) {
  "use strict";
  if (row_index < 0 || row_index >= this.rows) {
    throw new Error("INDEX_OUT_OF_BOUNDS");
  }
  return this.pspm_column[row_index];
};

Logo.prototype._as_eps_data = function(ssc, errbars) {
  var i, j, pos, stack_pos, pspm, stack, sym, out;
  out = "";
  for (i = 0; i < this.rows; i++) {
    out += "\nStartLine\n";
    // Indent
    for (j = 0; j < this.pspm_column[i]; j++) {
      out += "() startstack\nendstack\n\n";
    }
    pspm = this.pspm_list[i];
    if (pspm.get_left_trim() > 0) {
      out += "MuteColour\nDrawTrimEdge\n" + pspm.get_left_trim() + " DrawTrimBg\n";
    }
    for (pos = 0; pos < pspm.get_motif_length(); pos++) {
      if (pos != 0 && pos == pspm.get_left_trim()) { // enable full colour
        out += "DrawTrimEdge\nRestoreColour\n";
      } else if (pos == (pspm.get_motif_length() - pspm.get_right_trim())) {
        out += "MuteColour\n" + pspm.get_right_trim() + " DrawTrimBg\n";
      }
      out += "(" + (pos + 1) + ") startstack\n";
      stack = pspm.get_stack(pos, this.alphabet, ssc);
      for (stack_pos = 0; stack_pos < stack.length; stack_pos++) {
        sym = stack[stack_pos];
        out += " " + (sym.get_scale() * this.alphabet.get_ic()) + " (" + sym.get_symbol() + ") numchar\n";
      }
      if (errbars) {
        out += " " + pspm.get_error(this.alphabet) + " Ibeam\n";
      }
      out += "endstack\n\n";
    }
    if (pspm.get_right_trim() > 0 || pspm.get_left_trim() == pspm.get_motif_length()) {
      out += "RestoreColour\n";
    }
    out += "EndLine\n";
  }
  return out;
};

Logo.prototype.as_eps = function(options) {
  "use strict";
  if (this.xlate_nsyms != 1) throw new Error("Unsupported setting xlate_nsyms for EPS");
  if (this.xlate_start != null) throw new Error("Unsupported setting xlate_start for EPS");
  if (this.xlate_end != null) throw new Error("Unsupported setting xlate_end for EPS");

  var LOGOHEIGHT = 7.5; // default height of line in cm
  var cm2pts, height, width, now, ssc, errbars;
  if (typeof options === "undefined") {
    options = {};
  }
  cm2pts = 72 / 2.54;
  if (typeof options.logo_height == "number") {
    height = options.logo_height;
  } else {
    height = LOGOHEIGHT * this.rows;
  }
  if (typeof options.logo_width == "number") {
    width = options.logo_width;
  } else {
    width = this.columns + 2;
  }
  now = new Date();
  ssc = (typeof options.ssc == "boolean" ? options.ssc : false);
  errbars = (typeof options.show_error_bar == "boolean" ? options.show_error_bar : ssc);
  var values = {
    "LOGOHEIGHT": height,
    "LOGOWIDTH": width,
    "BOUNDINGHEIGHT": Math.round(height * cm2pts),
    "BOUNDINGWIDTH": Math.round(width * cm2pts),
    "LOGOLINEHEIGHT": (height / this.rows),
    "CHARSPERLINE": this.columns,
    "BARBITS": this.alphabet.get_ic(),
    "LOGOTYPE": (this.alphabet.has_complement() ? "NA" : "AA"),
    "CREATIONDATE": now.getDate() + "." + (now.getMonth() + 1) + "." + now.getFullYear() + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds(),
    "ERRORBARFRACTION": (typeof options.error_bar_fraction == "number" ? options.error_bar_fraction : 1.0),
    "TICBITS": (typeof options.ticbits == "number" ? options.ticbits : 1.0),
    "TITLE": (typeof options.title == "string" ? options.title : ""),
    "FINEPRINT": (typeof options.fineprint == "string" ? options.fineprint : this.fine_text),
    "XAXISLABEL": (typeof options.xaxislabel == "string" ? options.xaxislabel : ""),
    "YAXISLABEL": (typeof options.yaxislabel == "string" ? options.yaxislabel : "bits"),
    "SSC": ssc,
    "YAXIS": (typeof options.show_y_axis == "boolean" ? options.show_y_axis : this.y_axis),
    "SHOWENDS": (typeof options.show_ends == "boolean" ? options.show_ends : false),
    "ERRBAR": errbars,
    "OUTLINE": (typeof options.show_outline == "boolean" ? options.show_outline : false),
    "NUMBERING": (typeof options.show_numbering == "boolean" ? options.show_numbering : this.x_axis != 0),
    "SHOWINGBOX": (typeof options.show_box == "boolean" ? options.show_box : false),
    "CREATOR": (typeof options.creator == "string" ? options.creator : "motif_logo.js"),
    "FONTSIZE": (typeof options.label_font_size == "number" ? options.label_font_size : 12),
    "TITLEFONTSIZE": (typeof options.title_font_size == "number" ? options.title_font_size : 12),
    "SMALLFONTSIZE": (typeof options.small_font_size == "number" ? options.small_font_size : 6),
    "TOPMARGIN" : (typeof options.top_margin == "number" ? options.top_margin : 0.9),
    "BOTTOMMARGIN": (typeof options.bottom_margin == "number" ? options.bottom_margin : 0.9),
    "COLORDICT": this.alphabet._as_eps_dict(),
    "DATA": this._as_eps_data(ssc, errbars)
  };
  // now this requires that the script containing the template has been imported!
  return motif_logo_template(values);
};

//======================================================================
// end Logo object
//======================================================================

// calculate the exact size (in pixels) of an object drawn on the
// canvas assuming that the background of the canvas is transparent.
function canvas_bounds(ctx, cwidth, cheight) {
  "use strict";
  var data, r, c, top_line, bottom_line, left_line, right_line, 
      txt_width, txt_height;

  // extract the image data
  data = ctx.getImageData(0, 0, cwidth, cheight).data;

  // set initial values
  top_line = -1; bottom_line = -1; left_line = -1; right_line = -1;
  txt_width = 0; txt_height = 0;

  // Find the top-most line with a non-transparent pixel
  for (r = 0; r < cheight; r++) {
    for (c = 0; c < cwidth; c++) {
      if (data[r * cwidth * 4 + c * 4 + 3]) {
        top_line = r;
        break;
      }
    }
    if (top_line != -1) {
      break;
    }
  }
  
  // Only bother looking if we found at least one set pixel... 
  if (top_line != -1) {

    //find the last line with a non-transparent pixel
    for (r = cheight-1; r >= top_line; r--) {
      for(c = 0; c < cwidth; c++) {
        if(data[r * cwidth * 4 + c * 4 + 3]) {
          bottom_line = r;
          break;
        }
      }
      if (bottom_line != -1) {
        break;
      }
    }
    // calculate height
    txt_height = bottom_line - top_line + 1;

    // Find the left-most line with a non-transparent pixel
    for (c = 0; c < cwidth; c++) {
      for (r = top_line; r <= bottom_line; r++) {
        if (data[r * cwidth * 4 + c * 4 + 3]) {
          left_line = c;
          break;
        }
      }
      if (left_line != -1) {
        break;
      }
    }

    //find the right most line with a non-transparent pixel
    for (c = cwidth-1; c >= left_line; c--) {
      for(r = top_line; r <= bottom_line; r++) {
        if(data[r * cwidth * 4 + c * 4 + 3]) {
          right_line = c;
          break;
        }
      }
      if (right_line != -1) {
        break;
      }
    }
    txt_width = right_line - left_line + 1;
  }

  //return the bounds
  return {bound_top: top_line, bound_bottom: bottom_line, 
    bound_left: left_line, bound_right: right_line, width: txt_width, 
    height: txt_height};
}

//======================================================================
// start RasterizedAlphabet
//======================================================================

// Rasterize Alphabet
// 1) Measure width of text at default font for all symbols in alphabet
// 2) sort in width ascending
// 3) Drop the top and bottom 10% (designed to ignore outliers like 'W' and 'I')
// 4) Calculate the average as the maximum scaling factor (designed to stop I becoming a rectangular blob).
// 5) Assume scale of zero would result in width of zero, interpolate scale required to make perfect width font
// 6) Draw text onto temp canvas at calculated scale
// 7) Find bounds of drawn text
// 8) Paint on to another canvas at the desired height (but only scaling width to fit if larger).
var RasterizedAlphabet = function(alphabet, logo_scale, font, width) {
  "use strict";
  var default_size, safety_pad, canvas, ctx, middle, baseline, widths, sizes,
      i, sym, size, tenpercent, avg_width, scale, 
      target_width, target_height;
  //variable prototypes
  this.alphabet = alphabet;
  this.scale = logo_scale;
  this.sym_cache = {};
  this.stack_num_cache = [];
  this.scale_num_cache = [];
  // size of canvas
  default_size = 60; // size of measuring canvas
  safety_pad = 20; // pixels to pad around so we don't miss the edges
  // create a canvas to do our measuring
  canvas = document.createElement("canvas");
  if (!canvas.getContext) throw new Error("No canvas support");
  canvas.width = default_size + 2 * safety_pad;
  canvas.height = default_size + 2 * safety_pad;
  middle = Math.round(canvas.width / 2);
  baseline = Math.round(canvas.height - safety_pad);
  ctx = canvas.getContext('2d');
  if (!supports_text(ctx)) throw new Error("Canvas does not support text");
  ctx.font = font;
  ctx.textAlign = "center";
  ctx.translate(middle, baseline);
  // list of widths
  widths = [];
  sizes = [];
  //now measure each letter in the alphabet
  for (i = 0; i < alphabet.get_size_core(); ++i) {
    // reset the canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = alphabet.get_colour(i);
    // draw the test text
    ctx.fillText(alphabet.get_symbol(i), 0, 0);
    //measure
    size = canvas_bounds(ctx, canvas.width, canvas.height);
    if (size.width === 0) throw new Error("Invisible symbol!");
    widths.push(size.width);
    sizes[i] = size;
  }
  //sort the widths
  widths.sort(function(a,b) {return a - b;});
  //drop 10% of the items off each end
  tenpercent = Math.floor(widths.length / 10);
  for (i = 0; i < tenpercent; ++i) {
    widths.pop();
    widths.shift();
  }
  //calculate average width
  avg_width = 0;
  for (i = 0; i < widths.length; ++i) {
    avg_width += widths[i];
  }
  avg_width /= widths.length;
  // calculate the target width
  target_width = width * this.scale * 2;
  // calculate scales
  for (i = 0; i < alphabet.get_size_core(); ++i) {
    sym = alphabet.get_symbol(i);
    size = sizes[i];
    // calculate scale
    scale = target_width / Math.max(avg_width, size.width);
    // estimate scaled height
    target_height = size.height * scale;
    // create an appropriately sized canvas
    canvas = document.createElement("canvas");
    canvas.width = target_width;
    canvas.height = target_height + safety_pad * 2;
    // calculate the middle
    middle = Math.round(canvas.width / 2);
    // calculate the baseline
    baseline = Math.round(canvas.height - safety_pad);
    // get the context and prepare to draw the rasterized text
    ctx = canvas.getContext('2d');
    ctx.font = font;
    ctx.fillStyle = alphabet.get_colour(i);
    ctx.textAlign = "center";
    ctx.translate(middle, baseline);
    ctx.save();
    ctx.scale(scale, scale);
    // draw the text
    ctx.fillText(sym, 0, 0);
    ctx.restore();
    this.sym_cache[sym] = {"image": canvas, "size": canvas_bounds(ctx, canvas.width, canvas.height)};
  }
};

RasterizedAlphabet.prototype.get_alphabet = function() {
  return this.alphabet;
};

RasterizedAlphabet.prototype.get_scale = function() {
  return this.scale;
};

RasterizedAlphabet.prototype.draw_stack_sym = function(ctx, letter, dx, dy, dWidth, dHeight) {
  "use strict";
  var entry, image, size;
  entry = this.sym_cache[letter];
  image = entry.image;
  size = entry.size;
  ctx.drawImage(image, 0, size.bound_top -1, image.width, size.height+1, dx, dy, dWidth, dHeight);
};

RasterizedAlphabet.prototype.draw_stack_num = function(ctx, font, stack_width, index) {
  var image, image_ctx, text_length;
  if (index >= this.stack_num_cache.length) {
    image = document.createElement("canvas");
    // measure the text
    image_ctx = image.getContext('2d');
    image_ctx.save();
    image_ctx.font = font;
    text_length = image_ctx.measureText("" + (index + 1)).width;
    image_ctx.restore();
    // resize the canvas to fit
    image.width = Math.ceil(stack_width);
    image.height = Math.ceil(text_length);
    // draw the text
    image_ctx = image.getContext('2d');
    image_ctx.translate(Math.round(stack_width / 2), 0);
    image_ctx.font = font;
    image_ctx.textBaseline = "middle";
    image_ctx.textAlign = "right";
    image_ctx.rotate(-(Math.PI / 2));
    image_ctx.fillText("" + (index + 1), 0, 0);
    this.stack_num_cache[index] = image;
  } else {
    image = this.stack_num_cache[index];
  }
  ctx.drawImage(image, 0, 0);
}

RasterizedAlphabet.prototype.draw_scale_num = function(ctx, font, num) {
  var image, image_ctx, text_size, m_length;
  if (num >= this.scale_num_cache.length) {
    image = document.createElement("canvas");
    // measure the text
    image_ctx = image.getContext('2d');
    image_ctx.font = font;
    text_size = image_ctx.measureText("" + num);
    if (text_size.actualBoundingBoxAscent && text_size.actualBoundingBoxDesent) {
      // resize the canvas to fit
      image.width = Math.ceil(text_size.width);
      image.height = Math.ceil(text_size.actualBoundingBoxAscent + text_size.actualBoundingBoxDesent);
      // draw the text
      image_ctx = image.getContext('2d');
      image_ctx.font = font;
      image_ctx.textAlign = "right";
      image_ctx.fillText("" + num, image.width, text_size.actualBoundingBoxAscent);
    } else {
      // measure width of 'm' to approximate height, we double it later anyway
      m_length = image_ctx.measureText("m").width;
      // resize the canvas to fit
      image.width = Math.ceil(text_size.width);
      image.height = Math.ceil(2 * m_length);
      // draw the text
      image_ctx = image.getContext('2d');
      image_ctx.font = font;
      image_ctx.textAlign = "right";
      image_ctx.textBaseline = "middle";
      image_ctx.fillText("" + num, image.width, m_length);
    }
    this.scale_num_cache[num] = image;
  } else {
    image = this.scale_num_cache[num];
  }
  ctx.drawImage(image, -image.width, -Math.round(image.height / 2))
}

//======================================================================
// end RasterizedAlphabet
//======================================================================

//======================================================================
// start LogoMetrics object
//======================================================================

var LogoMetrics = function(ctx, logo_columns, logo_rows, has_names, has_finetext, x_axis, y_axis) {
  "use strict";
  var i, row_height;
  //variable prototypes
  this.pad_top = (has_names ? 5 : 0);
  this.pad_left = (y_axis ? 10 : 0);
  this.pad_right = (has_finetext ? 15 : 0);
  this.pad_bottom = 0;
  this.pad_middle = 20;
  this.name_height = 14;
  this.name_font = "bold " + this.name_height + "px Times, sans-serif";
  this.name_spacer = 0;
  this.y_axis = y_axis;
  this.y_label = "bits";
  this.y_label_height = 12;
  this.y_label_font = "bold " + this.y_label_height + "px Helvetica, sans-serif";
  this.y_label_spacer = 3;
  this.y_num_height = 12;
  this.y_num_width = 0;
  this.y_num_font = "bold " + this.y_num_height + "px Helvetica, sans-serif";
  this.y_tic_width = 5;
  this.stack_pad_left = 0;
  this.stack_font = "bold 25px Helvetica, sans-serif";
  this.stack_height = 90;
  this.stack_width = 26;
  this.stacks_pad_right = 5;
  this.x_axis = x_axis;
  this.x_num_above = 2;
  this.x_num_height = 12;
  this.x_num_width = 0;
  this.x_num_font = "bold " + this.x_num_height + "px Helvetica, sans-serif";
  this.fine_txt_height = 6;
  this.fine_txt_above = 2;
  this.fine_txt_font = "normal " + this.fine_txt_height + "px Helvetica, sans-serif";
  this.letter_metrics = new Array();
  this.summed_width = 0;
  this.summed_height = 0;
  //calculate the width of the y axis numbers
  ctx.font = this.y_num_font;
  for (i = 0; i <= 2; i++) {
    this.y_num_width = Math.max(this.y_num_width, ctx.measureText("" + i).width);
  }
  //calculate the width of the x axis numbers (but they are rotated so it becomes height)
  if (x_axis == 1) {
    ctx.font = this.x_num_font;
    for (i = 1; i <= logo_columns; i++) {
      this.x_num_width = Math.max(this.x_num_width, ctx.measureText("" + i).width);
    }
  } else if (x_axis == 0) {
    this.x_num_height = 4;
    this.x_num_width = 4;
  } else {
    this.x_num_height = 0;
    this.x_num_width = 0;
  }
  
  //calculate how much vertical space we want to draw this
  //first we add the padding at the top and bottom since that's always there
  this.summed_height += this.pad_top + this.pad_bottom;
  //all except the last row have the same amount of space allocated to them
  if (logo_rows > 1) {
    row_height = this.stack_height + this.pad_middle;
    if (has_names) {
      row_height += this.name_height;
      //the label is allowed to overlap into the spacer
      row_height += Math.max(this.y_num_height/2, this.name_spacer); 
      //the label is allowed to overlap the space used by the other label
      row_height += Math.max(this.y_num_height/2, this.x_num_height + this.x_num_above); 
    } else {
      row_height += this.y_num_height/2; 
      //the label is allowed to overlap the space used by the other label
      row_height += Math.max(this.y_num_height/2, this.x_num_height + this.x_num_above); 
    }
    this.summed_height += row_height * (logo_rows - 1);
  }
  //the last row has the name and fine text below it but no padding
  this.summed_height += this.stack_height + (this.y_axis ? this.y_num_height/2 : 0);

  var fine_txt_total = (has_finetext ? this.fine_txt_height + this.fine_txt_above : 0);
  if (has_names) {
    this.summed_height += fine_txt_total + this.name_height;
    this.summed_height += Math.max((this.y_axis ? this.y_num_height/2 : 0), 
        this.x_num_height + this.x_num_above + this.name_spacer);
  } else {
    this.summed_height += Math.max((this.y_axis ? this.y_num_height/2 : 0), 
        this.x_num_height + this.x_num_above + fine_txt_total);
  }

  //calculate how much horizontal space we want to draw this
  //first add the padding at the left and right since that's always there
  this.summed_width += this.pad_left + this.pad_right;
  if (this.y_axis) {
    //add on the space for the y-axis label
    this.summed_width += this.y_label_height + this.y_label_spacer;
    //add on the space for the y-axis
    this.summed_width += this.y_num_width + this.y_tic_width;
  }
  //add on the space for the stacks
  this.summed_width += (this.stack_pad_left + this.stack_width) * logo_columns;
  //add on the padding after the stacks (an offset from the fine text)
  this.summed_width += this.stacks_pad_right;

};

//======================================================================
// end LogoMetrics object
//======================================================================

//found this trick at http://talideon.com/weblog/2005/02/detecting-broken-images-js.cfm
function image_ok(img) {
  "use strict";
  // During the onload event, IE correctly identifies any images that
  // weren't downloaded as not complete. Others should too. Gecko-based
  // browsers act like NS4 in that they report this incorrectly.
  if (!img.complete) {
    return false;
  }
  // However, they do have two very useful properties: naturalWidth and
  // naturalHeight. These give the true size of the image. If it failed
  // to load, either of these should be zero.
  if (typeof img.naturalWidth !== "undefined" && img.naturalWidth === 0) {
    return false;
  }
  // No other way of checking: assume it's ok.
  return true;
}
  
function supports_text(ctx) {
  "use strict";
  if (!ctx.fillText) {
    return false;
  }
  if (!ctx.measureText) {
    return false;
  }
  return true;
}

//draws the scale, returns the width
function draw_scale(ctx, metrics, alphabet_ic, raster) {
  "use strict";
  var tic_height, i;
  tic_height = metrics.stack_height / alphabet_ic;
  ctx.save();
  ctx.translate(metrics.y_label_height, metrics.y_num_height/2);
  //draw the axis label
  ctx.save();
  ctx.font = metrics.y_label_font;
  ctx.translate(0, metrics.stack_height/2);
  ctx.rotate(-(Math.PI / 2));
  ctx.textAlign = "center";
  ctx.fillText("bits", 0, 0);
  ctx.restore();

  ctx.translate(metrics.y_label_spacer + metrics.y_num_width, 0);

  //draw the axis tics
  ctx.save();
  ctx.translate(0, metrics.stack_height);
  for (i = 0; i <= alphabet_ic; i++) {
    //draw the number
    ctx.save();
    ctx.translate(-1, 0);
    raster.draw_scale_num(ctx, metrics.y_num_font, i);
    ctx.restore();
    //draw the tic
    ctx.fillRect(0, -1, metrics.y_tic_width, 2);
    //prepare for next tic
    ctx.translate(0, -tic_height);
  }
  ctx.restore();

  ctx.fillRect(metrics.y_tic_width - 2, 0, 2, metrics.stack_height)

  ctx.restore();
}

function draw_stack_num(ctx, metrics, row_index, raster) {
  "use strict";
  ctx.save();
  ctx.translate(0, Math.round(metrics.stack_height + metrics.x_num_above));
  if (metrics.x_axis == 1) {
    raster.draw_stack_num(ctx, metrics.x_num_font, metrics.stack_width, row_index);
  } else if (metrics.x_axis == 0) {
    // draw dots instead of the numbers (good for small logos)
    ctx.beginPath();
    var radius = Math.round(metrics.x_num_height / 2);
    ctx.arc(Math.round(metrics.stack_width / 2), radius, radius, 0, 2 * Math.PI, false);
    ctx.fill();
  }
  ctx.restore();
}

function draw_stack(ctx, metrics, symbols, raster) {
  "use strict";
  var preferred_pad, sym_min, i, sym, sym_height, pad;
  preferred_pad = 0;
  sym_min = 5;

  ctx.save();//1
  ctx.translate(0, metrics.stack_height);
  for (i = 0; i < symbols.length; i++) {
    sym = symbols[i];
    sym_height = metrics.stack_height * sym.get_scale();
    
    pad = preferred_pad;
    if (sym_height - pad < sym_min) {
      pad = Math.min(pad, Math.max(0, sym_height - sym_min));
    }
    sym_height -= pad;

    //translate to the correct position
    ctx.translate(0, -(pad/2 + sym_height));

    //draw
    raster.draw_stack_sym(ctx, sym.get_symbol(), 0, 0, metrics.stack_width, sym_height);
    //translate past the padding
    ctx.translate(0, -(pad/2));
  }
  ctx.restore();//1
}

function draw_dashed_line(ctx, pattern, start, x1, y1, x2, y2) {
  "use strict";
  var x, y, len, i, dx, dy, tlen, theta, mulx, muly, lx, ly;
  dx = x2 - x1;
  dy = y2 - y1;
  tlen = Math.pow(dx*dx + dy*dy, 0.5);
  theta = Math.atan2(dy,dx);
  mulx = Math.cos(theta);
  muly = Math.sin(theta);
  lx = [];
  ly = [];
  for (i = 0; i < pattern; ++i) {
    lx.push(pattern[i] * mulx);
    ly.push(pattern[i] * muly);
  }
  i = start;
  x = x1;
  y = y1;
  len = 0;
  ctx.beginPath();
  while (len + pattern[i] < tlen) {
    ctx.moveTo(x, y);
    x += lx[i];
    y += ly[i];
    ctx.lineTo(x, y);
    len += pattern[i];
    i = (i + 1) % pattern.length;
    x += lx[i];
    y += ly[i];
    len += pattern[i];
    i = (i + 1) % pattern.length;
  }
  if (len < tlen) {
    ctx.moveTo(x, y);
    x += mulx * (tlen - len);
    y += muly * (tlen - len);
    ctx.lineTo(x, y);
  }
  ctx.stroke();
}

function draw_trim_background(ctx, metrics, left_start, left_end, left_divider, right_start, right_end, right_divider) {
  "use strict";
  var left_size = left_end - left_start;
  var right_size = right_end - right_start;
  var line_x;

  ctx.save();//s8
  ctx.fillStyle = "rgb(240, 240, 240)";
  if (left_size > 0) {
    ctx.fillRect(left_start * metrics.stack_width, 0, left_size * metrics.stack_width, metrics.stack_height);
  }
  if (right_size > 0) {
    ctx.fillRect(right_start * metrics.stack_width, 0, right_size * metrics.stack_width, metrics.stack_height);
  }
  ctx.fillStyle = "rgb(51, 51, 51)";
  if (left_size > 0 && left_divider) {
    line_x = (left_end * metrics.stack_width) - 0.5;
    draw_dashed_line(ctx, [3], 0, line_x, 0, line_x, metrics.stack_height);
  }
  if (right_size > 0 && right_divider) {
    line_x = (right_start * metrics.stack_width) + 0.5;
    draw_dashed_line(ctx, [3], 0, line_x, 0, line_x, metrics.stack_height);
  }
  ctx.restore();//s8
}

function size_logo_on_canvas(logo, canvas, show_names, scale) {
  "use strict";
  var draw_name, draw_finetext, metrics;
  draw_name = (typeof show_names === "boolean" ? show_names : (logo.get_rows() > 1));
  draw_finetext = (logo.fine_text.length > 0);
  if (canvas.width !== 0 && canvas.height !== 0) {
    return;
  }
  metrics = new LogoMetrics(canvas.getContext('2d'), 
      logo.get_xlate_columns(), logo.get_rows(), draw_name, draw_finetext, logo.x_axis, logo.y_axis);
  if (typeof scale == "number") {
    //resize the canvas to fit the scaled logo
    canvas.width = metrics.summed_width * scale;
    canvas.height = metrics.summed_height * scale;
  } else {
    if (canvas.width === 0 && canvas.height === 0) {
      canvas.width = metrics.summed_width;
      canvas.height = metrics.summed_height;
    } else if (canvas.width === 0) {
      canvas.width = metrics.summed_width * (canvas.height / metrics.summed_height);
    } else if (canvas.height === 0) {
      canvas.height = metrics.summed_height * (canvas.width / metrics.summed_width);
    }
  }
}

function draw_logo_on_canvas(logo, canvas, show_names, scale) {
  "use strict";
  var i, draw_name, draw_finetext, ctx, metrics, raster, pspm_i, pspm, 
      offset, col_index, motif_position, ssc;
  ssc = false;
  draw_name = (typeof show_names === "boolean" ? show_names : (logo.get_rows() > 1));
  draw_finetext = (logo.fine_text.length > 0);
  ctx = canvas.getContext('2d');
  //assume that the user wants the canvas scaled equally so calculate what the best width for this image should be
  metrics = new LogoMetrics(ctx, logo.get_xlate_columns(), logo.get_rows(), draw_name, draw_finetext, logo.x_axis, logo.y_axis);
  if (typeof scale == "number") {
    //resize the canvas to fit the scaled logo
    canvas.width = metrics.summed_width * scale;
    canvas.height = metrics.summed_height * scale;
  } else {
    if (canvas.width === 0 && canvas.height === 0) {
      scale = 1;
      canvas.width = metrics.summed_width;
      canvas.height = metrics.summed_height;
    } else if (canvas.width === 0) {
      scale = canvas.height / metrics.summed_height;
      canvas.width = metrics.summed_width * scale;
    } else if (canvas.height === 0) {
      scale = canvas.width / metrics.summed_width;
      canvas.height = metrics.summed_height * scale;
    } else {
      scale = Math.min(canvas.width / metrics.summed_width, canvas.height / metrics.summed_height);
    }
  }
  // cache the raster based on the assumption that we will be drawing a lot
  // of logos the same size and alphabet
  if (typeof draw_logo_on_canvas.raster_cache === "undefined") {
    draw_logo_on_canvas.raster_cache = [];
  }
  for (i = 0; i < draw_logo_on_canvas.raster_cache.length; i++) {
    raster = draw_logo_on_canvas.raster_cache[i];
    if (raster.get_alphabet().equals(logo.alphabet) &&
        Math.abs(raster.get_scale() - scale) < 0.1) break;
    raster = null;
  }
  if (raster == null) {
    raster = new RasterizedAlphabet(logo.alphabet, scale, metrics.stack_font, metrics.stack_width);
    draw_logo_on_canvas.raster_cache.push(raster);
  }
  ctx = canvas.getContext('2d');
  ctx.save();//s1
  ctx.scale(scale, scale);
  ctx.save();//s2
  ctx.save();//s7
  //create margin
  ctx.translate(Math.round(metrics.pad_left), Math.round(metrics.pad_top));
  for (pspm_i = 0; pspm_i < logo.get_rows(); ++pspm_i) {
    pspm = logo.get_pspm(pspm_i);
    offset = logo.get_offset(pspm_i);
    //optionally draw name if this isn't the last row or is the only row 
    if (draw_name && (logo.get_rows() == 1 || pspm_i != (logo.get_rows()-1))) {
      ctx.save();//s4
      ctx.translate(Math.round(metrics.summed_width/2), Math.round(metrics.name_height));
      ctx.font = metrics.name_font;
      ctx.textAlign = "center";
      ctx.fillText(pspm.name, 0, 0);
      ctx.restore();//s4
      ctx.translate(0, Math.round(metrics.name_height + 
          Math.min(0, metrics.name_spacer - metrics.y_num_height/2)));
    }
    //draw scale
    if (logo.y_axis) draw_scale(ctx, metrics, logo.alphabet.get_ic(), raster);
    ctx.save();//s5
    //translate across past the scale
    if (logo.y_axis) {
      ctx.translate(Math.round(metrics.y_label_height + metrics.y_label_spacer + 
        metrics.y_num_width + metrics.y_tic_width), Math.round(metrics.y_num_height / 2));
    }
    //draw the trimming background
    if (pspm.get_left_trim() > 0 || pspm.get_right_trim() > 0) {
      var left_start = offset * logo.get_xlate_nsyms();
      var left_end = (offset + pspm.get_left_trim()) * logo.get_xlate_nsyms();
      var left_divider = true;
      if (left_end < logo.get_xlate_start() || left_start > logo.get_xlate_end()) {
        // no overlap
        left_start = 0;
        left_end = 0;
        left_divider = false;
      } else {
        if (left_start < logo.get_xlate_start()) {
          left_start = logo.get_xlate_start();
        }
        if (left_end > logo.get_xlate_end()) {
          left_end = logo.get_xlate_end();
          left_divider = false;
        }
        left_start -= logo.get_xlate_start();
        left_end -= logo.get_xlate_start();
        if (left_end < left_start) {
          left_start = 0;
          left_end = 0;
          left_divider = false;
        }
      }
      var right_end = (offset + pspm.get_motif_length()) * logo.get_xlate_nsyms();
      //var right_start = right_end - (pspm.get_left_trim() * logo.get_xlate_nsyms());
      var right_start = right_end - (pspm.get_right_trim() * logo.get_xlate_nsyms());
      var right_divider = true;
      if (right_end < logo.get_xlate_start() || right_start > logo.get_xlate_end()) {
        // no overlap
        right_start = 0;
        right_end = 0;
        right_divider = false;
      } else {
        if (right_start < logo.get_xlate_start()) {
          right_start = logo.get_xlate_start();
          right_divider = false;
        }
        if (right_end > logo.get_xlate_end()) {
          right_end = logo.get_xlate_end();
        }
        right_start -= logo.get_xlate_start();
        right_end -= logo.get_xlate_start();
        if (right_end < right_start) {
          right_start = 0;
          right_end = 0;
          right_divider = false;
        }
      }
      draw_trim_background(ctx, metrics, left_start, left_end, left_divider, right_start, right_end, right_divider);
    }
    //draw letters
    var xlate_col;
    for (xlate_col = logo.get_xlate_start(); xlate_col < logo.get_xlate_end(); xlate_col++) {
      ctx.translate(metrics.stack_pad_left,0);
      col_index = Math.floor(xlate_col / logo.get_xlate_nsyms());
      if (xlate_col % logo.get_xlate_nsyms() == 0) {
        if (col_index >= offset && col_index < (offset + pspm.get_motif_length())) {
          motif_position = col_index - offset;
          draw_stack_num(ctx, metrics, motif_position, raster);
          draw_stack(ctx, metrics, pspm.get_stack(motif_position, logo.alphabet, ssc), raster);
        }
      } else {
        if (col_index >= offset && col_index < (offset + pspm.get_motif_length())) {
          ctx.save();// s5.1
          ctx.translate(0, Math.round(metrics.stack_height));
          // TODO draw a dot or dash or something to indicate continuity of the motif
          ctx.restore(); //s5.1
        }
      }
      ctx.translate(Math.round(metrics.stack_width), 0);
    }
    ctx.restore();//s5
    ////optionally draw name if this is the last row but isn't the only row 
    if (draw_name && (logo.get_rows() != 1 && pspm_i == (logo.get_rows()-1))) {
      //translate vertically past the stack and axis's        
      ctx.translate(0, metrics.y_num_height/2 + metrics.stack_height + 
          Math.max(metrics.y_num_height/2, metrics.x_num_above + metrics.x_num_width + metrics.name_spacer));

      ctx.save();//s6
      ctx.translate(metrics.summed_width/2, metrics.name_height);
      ctx.font = metrics.name_font;
      ctx.textAlign = "center";
      ctx.fillText(pspm.name, 0, 0);
      ctx.restore();//s6
      ctx.translate(0, metrics.name_height);
    } else {
      //translate vertically past the stack and axis's        
      ctx.translate(0, metrics.y_num_height/2 + metrics.stack_height + 
          Math.max(metrics.y_num_height/2, metrics.x_num_above + metrics.x_num_width));
    }
    //if not the last row then add middle padding
    if (pspm_i != (logo.get_rows() -1)) {
      ctx.translate(0, metrics.pad_middle);
    }
  }
  ctx.restore();//s7
  if (logo.fine_text.length > 0) {
    ctx.translate(metrics.summed_width - metrics.pad_right, metrics.summed_height - metrics.pad_bottom);
    ctx.font = metrics.fine_txt_font;
    ctx.textAlign = "right";
    ctx.fillText(logo.fine_text, 0,0);
  }
  ctx.restore();//s2
  ctx.restore();//s1
}

function create_canvas(c_width, c_height, c_id, c_title, c_display) {
  "use strict";
  var canvas = document.createElement("canvas");
  //check for canvas support before attempting anything
  if (!canvas.getContext) {
    return null;
  }
  var ctx = canvas.getContext('2d');
  //check for html5 text drawing support
  if (!supports_text(ctx)) {
    return null;
  }
  //size the canvas
  canvas.width = c_width;
  canvas.height = c_height;
  canvas.id = c_id;
  canvas.title = c_title;
  canvas.style.display = c_display;
  return canvas;
}

function logo_1(alphabet, fine_text, pspm) {
  "use strict";
  var logo = new Logo(alphabet, fine_text);
  logo.add_pspm(pspm);
  return logo;
}

function logo_2(alphabet, fine_text, target, query, query_offset) {
  "use strict";
  var logo = new Logo(alphabet, fine_text);
  if (query_offset < 0) {
    logo.add_pspm(target, -query_offset);
    logo.add_pspm(query);
  } else {
    logo.add_pspm(target);
    logo.add_pspm(query, query_offset);
  }      
  return logo;
}

/*
 * Specifies an alternate source for an image.
 * If the image with the image_id specified has
 * not loaded then a generated logo will be used 
 * to replace it.
 *
 * Note that the image must either have dimensions
 * or a scale must be set.
 */
function alternate_logo(logo, image_id, scale) {
  "use strict";
  var image = document.getElementById(image_id);
  if (!image) {
    alert("Can't find specified image id (" +  image_id + ")");
    return;
  }
  //if the image has loaded then there is no reason to use the canvas
  if (image_ok(image)) {
    return;
  }
  //the image has failed to load so replace it with a canvas if we can.
  var canvas = create_canvas(image.width, image.height, image_id, image.title, image.style.display);
  if (canvas === null) {
    return;
  }
  //draw the logo on the canvas
  draw_logo_on_canvas(logo, canvas, null, scale);
  //replace the image with the canvas
  image.parentNode.replaceChild(canvas, image);
}

/*
 * Specifies that the element with the specified id
 * should be replaced with a generated logo.
 */
function replace_logo(logo, replace_id, scale, title_txt, display_style) {
  "use strict";
  var element = document.getElementById(replace_id);
  if (!replace_id) {
    alert("Can't find specified id (" + replace_id + ")");
    return;
  }
  //found the element!
  var canvas = create_canvas(50, 120, replace_id, title_txt, display_style);
  if (canvas === null) {
    return;
  }
  //draw the logo on the canvas
  draw_logo_on_canvas(logo, canvas, null, scale);
  //replace the element with the canvas
  element.parentNode.replaceChild(canvas, element);
}

/*
 * Fast string trimming implementation found at
 * http://blog.stevenlevithan.com/archives/faster-trim-javascript
 *
 * Note that regex is good at removing leading space but
 * bad at removing trailing space as it has to first go through
 * the whole string.
 */
function trim (str) {
  "use strict";
  var ws, i;
  str = str.replace(/^\s\s*/, '');
  ws = /\s/; i = str.length;
  while (ws.test(str.charAt(--i)));
  return str.slice(0, i + 1);
}

    </script>
    <script type="text/javascript">
//
// simple-shared-doc.js
//
// Function to replace the innerHTML of element "id" with the HTML indicated by "doc_type".
// Easier to read and update than the more flexible approach in shared-doc.js. 
//
function print_doc(id, doc_type) {
  var html;
  switch (doc_type) {
    case 'motif-consensus':
      html = `
	<p id="consensus_doc"> 
	   A <b>consensus sequence</b> is constructed from each column in a
	   motif's frequency matrix using the <b>"50% rule"</b>
	   as follows:
	</p>
	<ol>
	  <li>The letter frequencies in the column are sorted in decreasing order.</li>
	  <li>Letters with frequency less 50% of the maximum are discarded.</li>
	  <li>The letter used in this position in the consensus sequence is determined
	  by the first rule below that applies:</li>
	  <ul>
	    <li>If there is only one letter left, or if the remaining letters exactly match
	    an ambiguous symbol in the alphabet, the <b>letter</b> or <b>ambiguous symbol</b>,
	    respectively, is used.</li>
	    <li>Otherwise, if the remaining set contains at least 50% of the core
	    symbols in the alphabet, the alphabet's <b>wildcard</b>
	    (e.g., "N" for DNA or RNA, and "X" for protein) is used.</li>
	    <li>Otherwise, the letter with the <b>maximum frequency</b> is used.</li>
	  </ul>
	</ol>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'ame-results-header':
      html = `
	<p>
          AME outputs a tab-separated values (TSV) file ('ame.tsv') that contains one line for each motif
	  found to be significantly enriched.
	  The lines are sorted in order of decreasing statistical significance.
          The first line contains the (tab-separated) names of the fields.
	  Your command line is given at the end of the file in a comment line starting with the
	  character '#'.
	</p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'ame-results-table':
      html = `
	<p>
	  The <a href="` + site_url + `/doc/ame-output-format.html#tsv_results">AME Results TSV Format</a> 
	  differs depending on the choice of method for measuring motif enrichment, as shown in the following table. 
	</p>
	<table style="width:100%" border=1>
	  <tr>
	    <th>Method</th>
	    <th colspan=19>Output Columns</th>
	  </tr>
	  <tr>
	    <th></th> 
	    <th>1</th>
	    <th>2</th>
	    <th>3</th>
	    <th>4</th>
	    <th>5</th>
	    <th>6</th>
	    <th>7</th>
	    <th>8</th>
	    <th>9</th>
	    <th>10</th>
	    <th>11</th>
	    <th>12</th>
	    <th>13 </th>
	    <th>14 </th>
	    <th>15 </th>
	    <th>16 </th>
	    <th>17 </th>
	    <th>18 </th>
	    <th>19 </th>
	  </tr>
	  <tr>
	    <td>fisher</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>FASTA_max </td>
	    <td>pos </td>
	    <td>neg </td>
	    <td>PWM_min</td>
	    <td>TP </td>
	    <td>%TP </td>
	    <td>FP </td>
	    <td>%FP </td>
	    </td> <td>
	    </td> <td>
	  </tr>
	  <tr>
	    <td>ranksum</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>FASTA_max </td>
	    <td>pos </td>
	    <td>neg</td>
	    <td>U </td>
	    <td>pleft </td>
	    <td>pright </td>
	    <td>pboth </td>
	    <td>adj_pleft </td>
	    <td>adj_pright </td>
	    <td>adj_both</td>
	  </tr>
	  <tr>
	    <td>3dmhg</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>FASTA_max </td>
	    <td>pos </td>
	    <td>neg</td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	  </tr>
	  <tr>
	    <td>4dmhg</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>FASTA_max </td>
	    <td>pos </td>
	    <td>neg</td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	  </tr>
	  <tr>
	    <td>pearson</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>Pearson_CC </td>
	    <td>mean_squared_error </td>
	    <td>slope </td>
	    <td>intercept</td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	  </tr>
	  <tr>
	    <td>spearman</td>
	    <td>rank </td>
	    <td>motif_DB </td>
	    <td>motif_ID </td>
	    <td>motif_alt_ID </td>
	    <td>consensus </td>
	    <td>p-value </td>
	    <td>adj_p-value </td>
	    <td>E-value</td>
	    <td>tests </td>
	    <td>Spearman_CC</td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	    </td> <td>
	  </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'ame-results-description':
      html = `
        <p>
          The names and meanings of each of the fields in <a href="` + site_url + `/doc/ame-output-format.html#tsv_results">AME Results TSV Format</a>,
          which depend on the method of enrichment analysis you chose, are described below.
        </p>
        <table class="dark" style="width:100%" border=1>
	  <tr> <th>method</th> <th>field</th> <th>name</th> <th>contents</th> </tr>	
    	  <tr> <td>all</td> <td>1</td> <td>rank</td> <td>The rank of the significance of the motif in the sorted results.</td> </tr>
    	  <tr> <td>all</td> <td>2</td> <td>motif_DB</td> <td> ` + get_doc_text('motif-db', 'the motif.') + `</td> </tr>
    	  <tr> <td>all</td> <td>3</td> <td>motif_ID</td> <td> ` + get_doc_text('motif-id') + `</td> </tr>
    	  <tr> <td>all</td> <td>4</td> <td>motif_alt_ID</td> <td> ` + get_doc_text('motif-alt-id') + `</td> </tr>
    	  <tr> <td>all</td> <td>5</td> <td>consensus</td> <td> ` + get_doc_text('motif-cons') + `</td> </tr>
    	  <tr> <td>all</td> <td>6</td> <td>p-value</td> <td> ` + get_doc_text('ame-pvalue') + `</td> </tr>
    	  <tr> <td>all</td> <td>7</td> <td>adj_p-value</td> <td> ` + get_doc_text('ame-adj-pvalue') + `</td> </tr>
    	  <tr> <td>all</td> <td>8</td> <td>E-value</td> <td> ` + get_doc_text('ame-evalue') + `</td> </tr>
    	  <tr> <td>all</td> <td>9</td> <td>tests</td> <td>The number of tests performed; used in correcting the <i>p</i>-value</td> </tr>
	  <tr> <td colspan=4></td>
          <tr> <td>fisher, ranksum, 3dmhg, 4dmhg</td> <td>10</td> <td>FASTA_max</td> <td>The optimal threshold for <b>labeling</b> sequences as positive;
            sequences with FASTA score less than or equal to the threshold are labeled as positive;
            this field will be contain the number of primary sequences if you provided
            a control file (using option <code>--control</code>); this field will contain the size of the
            partition if you specified one (using option <code>--fix-partition</code>).</td> </tr>
          <tr> <td>fisher, ranksum, 3dmhg, 4dmhg</td> <td>11</td> <td>pos</td> <td>The number of sequences <b>labeled</b> as positive.</td> </tr>
          <tr> <td>fisher, ranksum, 3dmhg, 4dmhg</td> <td>12</td> <td>neg</td> <td>The number of sequences <b>labeled</b> as negative.</td> </tr>
	  <tr> <td colspan=4></td>
          <tr> <td>fisher</td> <td>13</td> <td>PWM_min</td> <td>The optimal threshold on PWM score for <b>classifying</b> sequences as positive;
                sequences with PWM score greater than or equal to the threshold are classified as positive.</td> </tr>
          <tr> <td>fisher</td> <td>14</td> <td>TP</td> <td>The number of true positive sequences: sequences both <b>labeled</b> and <b>classified</b> as positive</td> </tr>
          <tr> <td>fisher</td> <td>15</td> <td>%TP</td> <td>The percentage of true positive sequences: percentage of sequences <b>labeled</b> positive and <b>classified</b> as positive.</td> </tr>
          <tr> <td>fisher</td> <td>16</td> <td>FP</td> <td>The number of false positive sequences: sequences <b>labeled</b> negative but <b>classified</b> as positive.</td> </tr>
          <tr> <td>fisher</td> <td>17</td> <td>%TP</td> <td>The percentage of false positive sequences: sequences <b>labeled</b> negative but <b>classified</b> as positive.</td> </tr>
	  <tr> <td colspan=4></td>
          <tr> <td>ranksum</td> <td>13</td> <td>U</td> <td>The value of the Mann-Whitney <i>U</i> statistic.</td> </tr>
          <tr> <td>ranksum</td> <td>14</td> <td>pleft</td> <td>The left-tailed <i>p</i>-value of the rank-sum test.</td> </tr>
          <tr> <td>ranksum</td> <td>15</td> <td>pright</td> <td>The right-tailed <i>p</i>-value of the rank-sum test.</td> </tr>
          <tr> <td>ranksum</td> <td>16</td> <td>pboth</td> <td>The two-tailed <i>p</i>-value of the rank-sum test.</td> </tr>
          <tr> <td>ranksum</td> <td>17</td> <td>adj_pleft</td> <td>The left-tailed <i>p</i>-value of the rank-sum test, adjusted for multiple tests.</td> </tr>
          <tr> <td>ranksum</td> <td>18</td> <td>adj_pright</td> <td>The right-tailed <i>p</i>-value of the rank-sum test, adjusted for multiple tests.</td> </tr>
          <tr> <td>ranksum</td> <td>19</td> <td>adj_both</td> <td>The two-tailed <i>p</i>-value of the rank-sum test, adjusted for multiple tests.</td> </tr>
	  <tr> <td colspan=4></td>
	  <tr> <td>pearson</td> <td>10</td> <td>Pearson_CC</td> <td>The correlation coefficient of the PWM and FASTA scores of positive sequences.</td> </tr>
	  <tr> <td>pearson</td> <td>11</td> <td>mean_squared_error</td> <td>The mean-squared error of the regression line between PWM and FASTA scores.</td> </tr>
	  <tr> <td>pearson</td> <td>12</td> <td>slope</td> <td>The slope of the regression line.</td> </tr>
	  <tr> <td>pearson</td> <td>13</td> <td>intercept</td> <td>The y-intercept of the regression line.</td> </tr>
	  <tr> <td colspan=4></td>
	  <tr> <td>spearman</td> <td>10</td> <td>Spearman_CC</td> <td>The correlation coefficient of the PWM and FASTA ranks of positive sequences./td> </tr>
        </table>
	  </ul>
	</p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'ame-sequences-tsv':
      html = `
        <p>AME outputs a tab-separated values (TSV) file ('sequences.tsv') containing one line for
	each sequence classified as 'positive' by AME for each significant motif.
        The first line contains the (tab-separated) names of the fields.
	The lines are grouped by motif, and groups are separated by a line
	starting with the character "#".
        </p>
        <p>
          The names and meanings of each of the fields in <a href="` + site_url + `/doc/ame-output-format.html#tsv_sequences">AME Significant Sequences TSV Format</a>,
          are described below.
        </p>
        <table class="dark" style="width:100%" border=1>
          <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
	  <tr> <td>1</td> <td>motif_DB</td> <td>` + get_doc_text('motif-db', 'the motif.') + `</td> </tr>
	  <tr> <td>2</td> <td>motif_ID</td> <td>the ID of the motif</td> </tr>
	  <tr> <td>3</td> <td>seq_ID</td> <td>the ID of the sequence</td> </tr>
	  <tr> <td>4</td> <td><i>label_score</i> (either FASTA_score or PWM_score)</td> <td>the value of the score used to label it as positive</td> </tr>
	  <tr> <td>5</td> <td><i>class_score</i> (either PWM_score or FASTA_score)</td> <td>the value of the score used to classify it as positive</td> </tr>
	  <tr> <td>6</td> <td>class</td> <td>whether the sequence is a true positive, 'tp', or a false positive, 'fp'</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'centrimo-results-tsv':
      html = `
	<p>
          CentriMo outputs a tab-separated values (TSV) file ('centrimo.tsv') that contains one line for each 
	  region found to be significantly enriched for a motif.
	  The lines are sorted in order of decreasing statistical significance.
          The first line contains the (tab-separated) names of the fields.
          Your command line is given at the end of the file in a comment line starting with the
          character '#'.
	</p>
	<p>
          The names and meanings of each of the fields in <a href="` + site_url + `/doc/centrimo-output-format.html#tsv_results">CentriMo Results TSV Format</a>,
          which depend on whether or not you provide control sequences to CentriMo, are described below.
	</p>
        <table class="dark" style="width:100%" border=1>
          <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
	  <tr> <td>1</td> <td>db_index</td> <td>The index of the motif file that contains the motif.  Motif
	      files are numbered in the order the appeared in the command line.</td> </tr>
	  <tr> <td>2</td> <td>motif_id</td> <td> ` + get_doc_text('motif-id') + `
	      If more than one motif has the same ID, CentriMo uses only the first such motif.
	      The name is single-quoted and preceded with '+' or '-' if you scanned separately with 
	      the reverse complement motif (using the <code>--sep</code> option).</td> </tr>
	  <tr> <td>3</td> <td>motif_alt_id</td> <td> ` + get_doc_text('motif-alt-id') + `</td> </tr>
	  <tr> <td>4</td> <td>consensus</td> <td> ` + get_doc_text('motif-cons') + `</td> </tr>
	  <tr> <td>5</td> <td>E-value</td> <td> ` + get_doc_text('centrimo-evalue') + `</td> </tr>
	  <tr> <td>6</td> <td>adj_p-value</td> <td> ` + get_doc_text('centrimo-adj-pvalue') + `
	      By default, a <i>p</i>-value is calculated by using the one-tailed binomial
	      test on the number of sequences with a match to the motif
	      that have their best match in the reported region;
              if you provided control sequences, the <i>p</i>-value of Fisher\'s exact test on the enrichment of 
              best matches in the positive sequences relative to the negative sequences is computed instead;
	      if you used the <code>--cd</code> option, the <i>p</i>-value is the probability that the average 
	      distance between the best site and the sequence center would be as low or lower than observed, 
	      computed using the cumulative Bates distribution, optimized over different score thresholds.
	      In all cases, the reported <i>p</i>-value has been adjusted for the number of regions
	      and/or score thresholds tested.</td> </tr>
	  <tr> <td>7</td> <td>log_adj_p-value</td> <td>Log of adjusted <i>p</i>-value.</td> </tr>
	  <tr> <td>8</td> <td>bin_location</td> <td>Location of the center of the most enriched region, or
		0 if you used the <code>--cd</code> option.
	  <tr> <td>9</td> <td>bin_width</td> <td> ` + get_doc_text('centrimo-bin-width') + `</td> </tr>
	  <tr> <td>10</td> <td>total_width</td> <td>The maximum number of regions possible for this motif
              <br>&nbsp;&nbsp;
	      round(sequence_length - motif_length + 1)/2,<br>
	      or the number of places the motif will fit if you used the <code>--cd</code> option.</td> </tr>
	  <tr> <td>11</td> <td>sites_in_bin</td> <td> ` + get_doc_text('centrimo-sites-in-bin') + `</td> </tr>
	  <tr> <td>12</td> <td>total_sites</td> <td>The number of sequences containing a match to the motif
	      above the score threshold.
	  <tr> <td>13</td> <td>p_success</td> <td>The probability of a random match falling into the enriched region:
	      <br>&nbsp;&nbsp;
	      bin_width / total_width</td> </tr>
	  <tr> <td>14</td> <td>p-value</td> <td>The uncorrected <i>p</i>-value before it gets adjusted for the
	      number of multiple tests to give the adjusted <i>p</i>-value.</td> </tr>
	  <tr> <td>15</td> <td>mult_tests</td> <td> ` + get_doc_text('centrimo-mult-tests') + `</td> </tr>
	  <tr> <th colspan=3>The following additional columns are present when you provide control sequences to CentriMo
	  (using the <code>--neg</code> option).</th> </tr>
	  <tr> <td>16</td> <td>neg_sites_in_bin</td> <td>The number of negative sequences where the best
	      match to the motif falls in the reported region.
	      This value is rounded but the underlying value may contain fractional counts.
	      Note: This number may be less than the number of negative have a best match in the region.
	      The reason for this is that a sequence may have many matches that score equally best.
	      If n matches have the best score in a sequence, 1/n is added to the
	      appropriate bin for each match.</td> </tr>
	  <tr> <td>17</td> <td>neg_sites</td> <td>The number of negative sequences containing a match to the
	      motif above the minimum score threshold.
	      When score optimization is enabled the score threshold may be raised
	      higher than the minimum.</td> </tr>
	  <tr> <td>18</td> <td>neg_adj_pvalue</td> <td>The probability that any tested region in the negative
	      sequences would be as enriched for best matches to this motif
	      according to the Binomial test.</td> </tr>
	  <tr> <td>19</td> <td>log_neg_adj_pvalue</td> <td>Log of negative adjusted <i>p</i>-value.</td> </tr>
	  <tr> <td>20</td> <td>fisher_adj_pvalue</td> <td>Fisher adjusted <i>p</i>-value before it gets adjusted for the
	      number of motifs in the input files(s).</td> </tr>
	  <tr> <td>21</td> <td>log_fisher_adj_pvalue</td> <td>Log of Fisher adjusted <i>p</i>-value.</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'centrimo-sites-txt':
      html = `
	<p>
          CentriMo outputs a text file ('site_counts.txt') that contains,
	  for each motif, pairs of values (bin_position, site_count),
	  or triples of values (bin_position, site_count, neg_site_count) if
	  you provided control sequences to CentriMo.
	  This data can be used to plot the density of motif best matches (sites)
	  along the input sequences.  Fractional counts are possible if multiple (n)
	  bins contain the best match for a given sequence, with each bin  
	  receiving an incremental count of 1/n. 
	</p>
	<p>
	  The data for each motif begins with a header line with the format:
	  <br>&nbsp&nbsp
		DB &lt;db_number&gt; MOTIF &lt;id&gt; &lt;alt&gt;
	  </br>
	  where &lt;id&gt; and &lt;alt&gt; are as described above.
	  The following lines (up to the next header line) 
	  each contain a single value-pair or value-triple for the motif
	  named in the header line.
	</p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'meme-chip-results-tsv':
      html = `
	<p>
          MEME-ChIP outputs a tab-separated values (TSV) file ('summary.tsv') that 
	  contains one line for each motif found by MEME-ChIP.
	  The lines are sorted in order of decreasing statistical significance.
	  The first line contains the (tab-separated) names of the fields.
	  Your command line is given at the end of the file in a comment line starting with the
	  character '#'.
	</p>
	<p>
          The names and meanings of the fields in the 
	  <a href="` + site_url + `/doc/meme-chip-output-format.html">MEME-ChIP Results Summary TSV Format</a> 
	  are described below. 
	</p>
        <table class="dark" style="width:100%" border=1>
          <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>MOTIF_INDEX</td> <td>The index of the motif in the "Motifs in MEME text format" file ('combined.meme') 
		output by MEME-ChIP.</td> </tr>
          <tr> <td>2</td> <td>MOTIF_SOURCE</td> <td>The name of the program that found the <i>de novo</i> motif, or the
                name of the motif file containing the known motif.</td> </tr>
          <tr> <td>3</td> <td>MOTIF_ID</td> <td> ` + get_doc_text('motif-id') + `</td> </tr>
          <tr> <td>4</td> <td>ALT_ID</td> <td> ` + get_doc_text('motif-alt-id') + `</td> </tr>
          <tr> <td>5</td> <td>CONSENSUS</td> <td>The ID of the <i>de novo</i> motif, or a consensus sequence
                computed from the letter frequencies in the known motif
                (as described <a href="#consensus_doc">below</a>).</td> </tr>
          <tr> <td>6</td> <td>WIDTH</td> <td>The width of the motif.</td> </tr>
          <tr> <td>7</td> <td>SITES</td> <td>The number of sites reported by the <i>de novo</i> program, or the number
                of "Total Matches" reported by CentriMo.</td> </tr>
          <tr> <td>8</td> <td>E-VALUE</td> <td>The statistical significance of the motif.</td> </tr>
          <tr> <td>9</td> <td>E-VALUE_SOURCE</td> <td>The program that reported the <i>E</i>-value.</td> </tr>
          <tr> <td>10</td> <td>MOST_SIMILAR_MOTIF</td> <td>The known motif most similar to this motif according to Tomtom.</td> </tr>
          <tr> <td>11</td> <td>URL</td> <td>A link to a description of the most similar motif, or to the known motif.</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'meme-chip-combined-motifs':
      html = `
        <p>
          MEME-ChIP outputs a text file ('combined.meme') containing all the significant motifs found by MEME-ChIP.
          The motifs are in <a href="` + site_url + `/doc/meme-format.html">Minimal MEME Motif format</a>, 
	  and their IDs correspond to the motif indices given in the "Summary in TSV Format" file ('summary.tsv').
        </p>
        </p>
          <b>Note:</b> The 'nsites=' and 'E=' fields in the motif headers are only
          relevant for the MEME and DREME motifs.  For known motifs, those values do
          not refer to the number of sites in the input sequences.
        </p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'spamo-results-tsv':
      html = `
	<p>
          SpaMo outputs a tab-separated values (TSV) file ('spamo.tsv') that contains one line for each motif
	  found to be significantly enriched.
	  The lines are grouped by secondary motif and sorted in order of decreasing statistical significance.
          The first line contains the (tab-separated) names of the fields.
	  Your command line is given at the end of the file in a comment line starting with the character '#'.
	</p>
	<p>
	  The names and meanings of each of the fields in <a href="` + site_url + `/doc/spamo-output-format.html#results-tsv">SpaMo Results TSV Format</a> 
	  are described below. 
	</p>
	<table class="dark" style="width:100%" border=1>
	  <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>prim_db</td> <td> ` + get_doc_text('motif-db', 'the primary motif.') + `</td> </tr>
          <tr> <td>2</td> <td>prim_id</td> <td> ` + get_doc_text('motif-id', 'primary') + `</td> </tr>
          <tr> <td>3</td> <td>prim_alt</td> <td> ` + get_doc_text('motif-alt-id', 'primary') + `</td> </tr>
          <tr> <td>4</td> <td>prim_cons</td> <td> ` + get_doc_text('motif-cons', 'primary') + `</td> </tr>
          <tr> <td>5</td> <td>sec_db</td> <td> ` + get_doc_text('motif-db', 'the secondary motif.') + `</td> </tr>
          <tr> <td>6</td> <td>sec_id</td> <td> ` + get_doc_text('motif-id', 'secondary') + `</td> </tr>
          <tr> <td>7</td> <td>sec_alt</td> <td> ` + get_doc_text('motif-alt-id', 'secondary') + `</td> </tr>
          <tr> <td>8</td> <td>sec_cons</td> <td> ` + get_doc_text('motif-cons', 'secondary') + `</td> </tr>
          <tr> <td>9</td> <td>trim_left</td> <td>Number of positions trimmed from left of secondary motif.</td> </tr>
          <tr> <td>10</td> <td>trim_right</td> <td>Number of positions trimmed from right of secondary motif.</td> </tr> 
          <tr> <th colspan=3>If the next three fields are not blank, the motif is redundant with a more significant ('parent') motif.</th> </tr>
          <tr> <td>11</td> <td>red_db</td> <td> ` + get_doc_text('motif-db', 'the parent motif.') + `</td> </tr>
          <tr> <td>12</td> <td>red_id</td> <td> ` + get_doc_text('motif-id', 'parent') + `</td> </tr>
          <tr> <td>13</td> <td>red_alt</td> <td> ` + get_doc_text('motif-alt-id', 'parent') + `</td> </tr>
          <tr> <td>14</td> <td>E-value</td> <td>The expected number motifs that would have least one spacing as enriched as the best spacing for this secondary. 
	    The <i>E</i>-value is the best spacing <i>p</i>-value multiplied by the number of motifs in the input database(s).</td> </tr>
          <tr> <td>15</td> <td>gap</td> <td>The distance between the edge of the primary and the (trimmed) secondary motif.</td> </tr>
          <tr> <td>16</td> <td>orient</td> <td>The (combination) of quadrants for which occurrences of this spacing are combined.</td> </tr>
          <tr> <td>17</td> <td>count</td> <td>The number of occurrences of the secondary motif with the given spacing and orientation to the primary motif.</td> </tr>
          <tr> <td>18</td> <td>total</td> <td>The total number of occurrences of the secondary motif within the margins around the best primary motif occurrence.</td> </tr>
          <tr> <td>19</td> <td>adj_p-value</td> <td>The <i>p</i>-value of the gap and orientation, adjusted for nine combinations of quadrants times the number of gaps tested (as controlled by the <code>-range</code> option).</td> </tr>
          <tr> <td>20</td> <td>p-value</td> <td>The <i>p</i>-value of the gap and orientation adjusted only for the number of gaps tested.</td> </tr>
        </table>
        <br>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'spamo-dumpseqs-tsv':
      html = `
	<p>
	  By specifying the options <code>--dumpseqs</code> or <code>--dumpsigs</code> 
	  you can have SpaMo create TSV (tab-separated values) files
	  describing the motif matches SpaMo used to make the histograms in its HTML output.
	  The files are named 
          '<code>seqs_&lt;primary_motif&gt;_&lt;secondary_db&gt;_&lt;secondary_motif&gt;.txt</code>'.
	  The rows in each file are sorted by sequence name.  
	  The first line contains the (tab-separated) names of the fields.
        </p>
	<p>
	  The names and meanings of each of the fields in <a href="` + site_url + `/doc/spamo-output-format.html#tsv_dumpseqs">SpaMo Dumpseqs TSV Format</a> 
	  are described below. 
        </p>
	<table class="dark" style="width:100%">
	  <tr><th>field</th><th>name</th><th>contents</th></tr>
	  <tr><td>1</td><td>matches</td><td>Trimmed lowercase sequence centered on primary match with matches in uppercase.</td></tr>
	  <tr><td>2</td><td>sec_pos</td><td>Position of the secondary match within the whole sequence.</td></tr>
	  <tr><td>3</td><td>pri_match</td><td>Sequence fragment that the primary matched.</td></tr>
	  <tr><td>4</td><td>pri_strand</td><td>Strand of the primary match (+/-).</td></tr>
	  <tr><td>5</td><td>sec_match</td><td>Sequence fragment that the secondary matched.</td></tr>
	  <tr><td>6</td><td>sec_strand</td><td>Strand of the secondary match (+/-).</td></tr>
	  <tr><td>7</td><td>same_opp</td><td>The primary match on the same (s) or opposite (o) strand as the secondary.</td></tr>
	  <tr><td>8</td><td>down_up</td><td>The secondary match is downstream (d) or upstream (u) of the primary.</td></tr>
	  <tr><td>9</td><td>gap</td><td>The gap between the primary and secondary matches.</td></tr>
	  <tr><td>10</td><td>seq_name</td><td>The name of the sequence.</td></tr>
	  <tr><td>11</td><td>adj_p-value</td><td>The <i>p</i>-value of the bin containing the match, adjusted for the number of bins.</td></tr>
	  <tr><th colspan="3">If the sequence names are in UCSC Genome Browser position
	  format (e.g., "chr5:36715616-36715623"), the following additional fields will be present:</th></tr>
	  <tr><td>12</td><td>pri_bed_chr</td><td>Position of primary match in BED coordinates.</td></tr>
	  <tr><td>13</td><td>pri_bed_start</td><td>"</td></tr>
	  <tr><td>14</td><td>pri_bed_end</td><td>"</td></tr>
	  <tr><td>15</td><td>pri_browser</td><td>Position of primary match in UCSC Genome Browser coordinates.</td></tr>
	  <tr><td>16</td><td>sec_bed_chr</td><td>Position of secondary match in BED coordinates.</td></tr>
	  <tr><td>16</td><td>sec_bed_start</td><td>"</td></tr>
	  <tr><td>16</td><td>sec_bed_end</td><td>"</td></tr>
	  <tr><td>19</td><td>sec_browser</td><td>Position of secondary match in UCSC Genome Browser coordinates.</td></tr>
	</table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'fimo-results-tsv':
      html = ` 
        <p>
          FIMO outputs a tab-separated values (TSV) file ('fimo.tsv') that contains one line for each
          significant match to a motif.
          The lines are sorted in order of decreasing statistical significance (increasing <i>p</i>-value).
          The first line contains the (tab-separated) names of the fields.
          Your command line is given at the end of the file in a comment line starting with the
          character '#'.
        </p>
	<p>
	  The names and meanings of each of the fields in <a href="` + site_url + `/doc/fimo-output-format.html#results-tsv">FIMO Results TSV Format</a> 
	  are described below. 
	</p>
	<table class="dark" style="width:100%" border=1>
	  <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>motif_id</td> <td> ` + get_doc_text('motif-id') + `</td> </tr>
          <tr> <td>2</td> <td>motif_alt_id</td> <td> ` + get_doc_text('motif-alt-id') + `</td> </tr>
          <tr> <td>3</td> <td>sequence_name</td> <td> ` + get_doc_text('sequence-id') + ` --OR-- the `
	    + get_doc_text('sequence-name') + `</td> </tr>
          <tr> <td>4</td> <td>start</td> <td> ` + get_doc_text('match-start-seq', 'motif occurrence') + ` --OR-- `
            + get_doc_text('match-start-genomic', 'motif occurrence') 
            + get_doc_text('parse-genomic-coord', 'The latter case occurs when FIMO') + `</td> </tr>
          <tr> <td>5</td> <td>stop</td> <td> ` + get_doc_text('match-stop-seq', 'motif occurrence') + ` --OR-- `
            + get_doc_text('match-stop-genomic', 'motif occurrence') 
            + get_doc_text('parse-genomic-coord', 'The latter case occurs when FIMO') + `</td> </tr>
          <tr> <td>6</td> <td>strand</td> <td>The strand '<code>+</code>' indicates the motif matched the forward
	    strand, '<code>-</code>' the reverse strand, and '<code>.</code>'
	    indicates strand is not applicable (as for amino acid sequences).</td> </tr>
          <tr> <td>7</td> <td>score</td> <td>The score for the motif occurrence. `
            + get_doc_text('motif-match-score') + `</td> </tr>
          <tr> <td>8</td> <td>p-value</td> <td> The <i>p</i>-value of the motif occurrence. `
            + get_doc_text('motif-match-p-value') + `</td> </tr>
          <tr> <td>9</td> <td>q-value</td> <td>The q-value of the motif occurrence. `
            + get_doc_text('bh-q-value', 'FIMO') + ` <b>Note:</b> This column is empty if you used the <span class=popt>--text</span> switch.</td> </tr>
          <tr> <td>10</td> <td>sequence</td> <td>The region of the sequence matched to the motif.</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'fimo-results-gff3':
      html = `
        <p>
          FIMO outputs a GFF3</a> file ('fimo.gff') that contains one line for each
          significant match to a motif.
        </p>
        <p>
	  The GFF3 format is described <a href="http://gmod.org/wiki/GFF3">here</a>.
	  The 'score' reported in the FIMO GFF3 output</a> (in column 6) is<br/>
	  &nbsp;&nbsp;&nbsp;&nbsp;<code>min(1000, -10*(log10(pvalue)))</code>, <br/>
	  and the 'display name' ('Name=' tag in column 9) is composed of the contents of three 
	  fields as follows <br/>
	  &nbsp;&nbsp;&nbsp;&nbsp;<code>&lt;motif_id&gt;_&lt;sequence_name&gt;&lt;strand&gt;</code>.
        </p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'gomo-results-tsv':
      html = `
        <p>
          GOMo outputs a tab-separated values (TSV) file ('gomo.tsv') that contains one line for each 
	  motif-GO-term pair found to be significantly enriched.
          The lines are grouped by motif and sorted in order of decreasing statistical significance.
          The first line contains the (tab-separated) names of the fields.
          Your command line is given at the end of the file in a comment line starting with the character '#'.
        </p>
        <p>
          The names and meanings of each of the fields in <a href="` + site_url + `/doc/gomo-output-format.html#results-tsv">GOMo Results TSV Format</a>
          are described below.
        </p>
        <table class="dark" style="width:100%" border=1>
          <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>Motif_Identifier</td> <td> ` + get_doc_text('motif-id') + ` </td> </tr> 
          <tr> <td>2</td> <td>GO_Term_Identifier</td> <td> ` + get_doc_text('gomo-go-term') + ` </td> </tr>
          <tr> <td>3</td> <td>GOMo_Score</td> <td> ` + get_doc_text('gomo-score') + ` </td> </tr>
          <tr> <td>4</td> <td>p-value</td> <td> ` + get_doc_text('gomo-p-value') + ` </td> </tr>
          <tr> <td>5</td> <td>q-value</td> <td> ` + get_doc_text('bh-q-value', 'GOMo') + ` </td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'gomo-results-xml':
      html = `
	<p>
	GOMo outputs an XML file ('gomo.xml') with the following format.
	</p>
	<table class="bordertable" border="1">
	  <tr>
	    <th>Tag</th><th>Child of</th><th>Description</th>
	  </tr>
	  <tr>
	    <td >&lt;gomo&gt;</td><td >Nothing</td>
	    <td>
	      Information about this run of GOMo.
	      <ul>
		<li>version - The version of GOMo that generated the XML file.</li>
		<li>release - The release date of the version that generated the XML.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td >&lt;program&gt;</td>
	    <td >&lt;gomo&gt;</td>
	    <td>
	      Information about the state of the program when it ran.<br />
	      <ul>
		<li>name - name of the program.</li>
		<li>cmd - the command line passed to the program.</li>
		<li>gene_url - the url used to lookup further information on the gene ids. 
		The url has ampersands (&amp;) converted into <b>&amp;amp;</b> and the place where
		  the gene ID should be replaced by <b>!!GENEID!!</b> .</li>
		<li>outdir - the output directory that the program wrote to.</li>
		<li>clobber - true if GOMo was allowed to overwrite the output directory.</li>
		<li>text_only - true if GOMo wrote to stdout, in which case this file would
		  not exist so it must be false.</li>
		<li>use_e_values - true if GOMo used <i>E</i>-values (converted from <i>p</i>-values) as 
		  input scores, false if GOMo used gene scores.</li>
		<li>score_e_thresh - if GOMo used <i>E</i>-values then this is the threshold that 
		  GOMo assumed the worst <i>E</i>-value (<i>p</i>-value = 1.0) for the gene to smooth out noise.</li>
		<li>min_gene_count - the minimum number of genes that a GO term was annotated 
		  with before GOMo would calculate a score for it.</li>
		<li>motifs - if present then a space delimited list of the motifs that GOMo
		  calculated a score for, otherwise GOMo scored all motifs.</li>
		<li>shuffle_scores - the number of times GOMo generated a shuffled mapping of
		  gene id to gene id to be used to generate scores from the null model.</li>
		<li>q_threshold - GOMo filtered the results to only show those with a better
		(smaller) q-value.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td>&lt;gomapfile&gt;</td>
	    <td>&lt;program&gt;</td>
	    <td>
	      Information about the GO mapping file.<br />
	      <ul>
		<li>path - the path to the mapping file.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td>&lt;seqscorefile&gt;</td>
	    <td>&lt;program&gt;</td>
	    <td>
	      Information about the sequence scoring file.<br />
	      <ul>
		<li>path - the path to the sequence scoring file.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td>&lt;motif&gt;</td>
	    <td>&lt;gomo&gt;</td>
	    <td>
	      Information about the motif.<br />
	      <ul>
		<li>id - the motif identifier.</li>
		<li>genecount - the number of scored sequences that were used to compute the result.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td>&lt;goterm&gt;</td>
	    <td>&lt;motif&gt;</td>
	    <td>
	      Information about the GO term.<br />
	      <ul>
		<li>id - the GO identifier.</li>
		<li>score - the geometric mean across all species of the rank-sum test <i>p</i>-value.</li>
		<li>pvalue - the empirically calculated <i>p</i>-value.</li>
		<li>qvalue - the empirically calculated q-value.</li>
		<li>annotated - the number of genes annotated with the go term.</li>
		<li>group - the subgroup that the term belongs to. For the Gene Ontology 
		    b = biological process, c = cellular component and m = molecular function.</li>
		<li>nabove - the number of more general terms that link to this one.</li>
		<li>nbelow - the number of more specific terms that link from this one.</li>
		<li>implied - is the go term implied by other significant go terms? 
		  Allows values 'y', 'n' or 'u' (default) for yes, no or unknown.</li>
		<li>description - the GO term description.</li>
	      </ul>
	    </td>
	  </tr>
	  <tr>
	    <td>&lt;gene&gt;</td>
	    <td>&lt;goterm&gt;</td>
	    <td>
	      Information about the GO term's annotated genes for the primary species.<br />
	      <ul>
		<li>id - the gene identifier.</li>
		<li>rank - the rank of the scored gene.</li>
	      </ul>
	    </td>
	  </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'mcast-results-tsv':
      html = ` 
        <p>
          MCAST outputs a tab-separated values (TSV) file ('mcast.tsv') that contains one line for each
          significant match of a cluster of motifs to a sequence region.
          The lines are sorted in order of decreasing statistical significance (increasing <i>p</i>-value).
          The first line contains the (tab-separated) names of the fields.
          Your command line is given at the end of the file in a comment line starting with the
          character '#'.
        </p>
	<p>
	  The names and meanings of each of the fields in 
          <a href="` + site_url + `/doc/mcast-output-format.html#results-tsv">MCAST Results TSV Format</a> 
	  are described below. 
	</p>
	<table class="dark" style="width:100%" border=1>
	  <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>pattern_name</td> <td>A unique name that MCAST generates for the match. </td> </tr>
          <tr> <td>2</td> <td>sequence_name</td> <td> ` + get_doc_text('sequence-id') + ` --OR-- the `
	    + get_doc_text('sequence-name') + `</td> </tr>
          <tr> <td>3</td> <td>start</td> <td> ` + get_doc_text('match-start-seq', 'matched sequence region') + ` --OR-- `
            + get_doc_text('match-start-genomic', 'motif occurrence')
            + get_doc_text('parse-genomic-coord', 'The latter case occurs when MCAST') + `</td> </tr>
          <tr> <td>4</td> <td>stop</td> <td> ` + get_doc_text('match-stop-seq', 'matched sequence region') + ` --OR-- `
            + get_doc_text('match-stop-genomic', 'motif occurrence')
            + get_doc_text('parse-genomic-coord', 'The latter case occurs when MCAST') + `</td> </tr>
          <tr> <td>5</td> <td>score</td> <td> ` + get_doc_text('mcast-cluster-score') + ` </td> </tr>
          <tr> <td>6</td> <td>p-value</td> <td> ` + get_doc_text('mcast-cluster-p-value') + ` </td> </tr>
          <tr> <td>7</td> <td>E-value</td> <td> ` + get_doc_text('mcast-cluster-E-value') + ` </td> </tr>
          <tr> <td>8</td> <td>q-value</td> <td> ` + get_doc_text('bh-q-value', 'MCAST') + ` 
	    <b>Note:</b> This column is empty if you used the <span class=popt>--text</span> switch.</td> </tr>
          <tr> <td>9</td> <td>sequence</td> <td>The region of the sequence matched to the motif cluster.</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'mcast-results-gff3':
      html = `
        <p>
          MCAST outputs a GFF3</a> file ('mcast.gff') that contains one line for each
          significant match to a motif cluster.
        </p>
        <p>
	  The GFF3 format is described <a href="http://gmod.org/wiki/GFF3">here</a>.
	  The 'score' reported in the MCAST GFF3 output</a> (in column 6) is<br/>
	  &nbsp;&nbsp;&nbsp;&nbsp;<code>min(1000, -10*(log10(pvalue)))</code>, <br/>
	  and the 'unique identifier' ('ID=' tag in column 9) is the value of the
	  &lt;pattern_name&gt; field in the MCAST results TSV format.  Following the
	  unique identifier in column 9, the <i>p</i>-value, <i>E</i>-value and q-value
	  of the match are given.
        </p>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    case 'tomtom-results-tsv':
      html = `
	<p>
          Tomtom outputs a tab-separated values (TSV) file ('tomtom.tsv') that contains one line for each motif
	  found to be significantly enriched.
	  The lines are grouped by query motif and sorted in order of decreasing statistical significance.
          The first line contains the (tab-separated) names of the fields.
	  Your command line is given at the end of the file in a comment line starting with the character '#'.
	</p>
	<p>
	  The names and meanings of each of the fields in <a href="` + site_url + `/doc/tomtom-output-format.html#results-tsv">Tomtom Results TSV Format</a> 
	  are described below. 
	</p>
	<table class="dark" style="width:100%" border=1>
	  <tr> <th>field</th> <th>name</th> <th>contents</th> </tr>
          <tr> <td>1</td> <td>Query_ID</td> <td> ` + get_doc_text('motif-id', 'query') + `</td> </tr>
          <tr> <td>2</td> <td>Target_ID</td> <td> ` + get_doc_text('motif-id', 'target') + `</td> </tr>
          <tr> <td>3</td> <td>Optimal_offset</td> <td> ` + get_doc_text('tomtom-offset') + `</td> </tr>
          <tr> <td>4</td> <td>p-value</td> <td> ` + get_doc_text('tomtom-p-value') + `</td> </tr>
          <tr> <td>5</td> <td>E-value</td> <td> ` + get_doc_text('tomtom-E-value') + `</td> </tr>
          <tr> <td>6</td> <td>q-value</td> <td> ` + get_doc_text('bh-q-value', 'Tomtom') + `</td> </tr>
          <tr> <td>7</td> <td>Overlap</td> <td> ` + get_doc_text('tomtom-overlap') + `</td> </tr>
          <tr> <td>8</td> <td>Query_consensus</td> <td>A consensus sequence
                computed from the letter frequencies in the query motif
                (as described <a href="#consensus_doc">below</a>).</td> </tr>
          <tr> <td>9</td> <td>Target_consensus</td> <td>A consensus sequence
                computed from the letter frequencies in the target motif
                (as described <a href="#consensus_doc">below</a>).</td> </tr>
          <tr> <td>10</td> <td>Orientation</td> <td> ` + get_doc_text('tomtom-orientation', "<br>A value of '+' means that the target motif is as it appears in the database. A value of '-' means that the reverse complement of the target motif is shown.") + `</td> </tr>
        </table>
      `;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
      break;

    default:
      html = "Error--Unrecognized doc_type: " + doc_type;
      document.getElementById(id).insertAdjacentHTML('beforeend', html);
  }
} // print_doc

//
// Function to return the HTML text of a given type.
// This function can be used directly to document the output format (xx-output-format.html)
// and indirectly via print_doc_para for help pop-ups in the actual output HTML,
// to prevent duplication of documentation.
//
function get_doc_text(doc_type, extra) {
  var html;
  if (extra == undefined) {extra = ""};

  switch (doc_type) {

    // shared
    case 'motif-db':
      return(`
	The name of a file of motifs ("motif database file") that contains ` + extra + `
      `);
    case 'motif-id':
      return(`
	The name of the ` + extra + ` motif, which is unique in the motif database file.
      `);
    case 'motif-alt-id':
      return(`
	An alternate name for the ` + extra + ` motif, which may be provided in the motif database file.
      `);
    case 'motif-width':
      return(`
	The width of the motif. No gaps are allowed in motifs supplied to ` + extra + `
        as it only works for motifs of a fixed width.
      `);
    case 'motif-cons':
      return(`
	A consensus sequence computed from the ` + extra + ` motif (as described <a href="#consensus_doc">below</a>).
      `);
    case 'motif-match-score':
     return(`
	The score for the match of a position in a sequence to a motif is
	computed by summing the appropriate entry from each column of the
	position-dependent scoring matrix that represents the motif. ` + extra + `
     `);
    case 'motif-match-p-value':
      return(`
	The <i>p</i>-value of a motif match is the probability of a single random
	subsequence of the length of the motif
	<a href="javascript:help_refine('pop_motif_match_score')">scoring</a>
	at least as well as the observed match.
      `);
    case 'bh-q-value-method':
      return(`
        ` + extra + ` estimates q-values from all the match <i>p</i>-values 
	using the method proposed by Benjamini & Hochberg (<i>Journal of the Royal Statistical Society B</i>, 57:289-300, 1995).
	See also Storey JD, Tibshirani R. Statistical significance for
	genome-wide studies, <i>Proc. Natl. Acad. Sci. USA</i> (2003) <b>100</b>:9440&ndash;9445.
      `);
    case 'bh-q-value':
      return(`
	The minimum False Discovery Rate (FDR) required to consider this match significant.</br>` + get_doc_text('bh-q-value-method', extra) + `
      `);
    case 'sdb-name':
      return(`
	The name of the (FASTA) sequence database file.
      `);
    case 'sdb-psp':
      return(`
	The name of the position specific priors (PSP) file.
      `);
    case 'sdb-dist':
      return(`
	The name of the binned distribution of priors file.
      `);
    case 'sdb-count':
      return(`
	The number of sequences in the database.
      `);
    case 'sdb-letters':
      return(`
	The number of letters in the sequence database.
      `);
    case 'lastmod':
      return(`
	The date of the last modification to the ` + extra + ` database.
      `);
    case 'sequence-id':
      return(`
        The identifier of the sequence (from the FASTA sequence header line)` + extra + `
      `);
    case 'sequence-name':
      return(`
	` + extra + `name of the sequence extracted from the sequence identifier (in the FASTA sequence header line).<br>
	When you use the <code>--parse-genomic--coord</code> option, the sequence name ends at the
	first colon ':' (if any) present in the sequence\'s FASTA identifier.  Typically this is the
	chromosome or contig name.  With the <code>--parse-genomic--coord</code> option,
	the start and stop positions are in 0-based coordinates relative to the sequence start given 
	in the FASTA sequence identifier (just after the sequence name).</td> </tr>
      `);
    case 'sequence-desc':
      return(`
        The description appearing after the identifier of the sequence in the FASTA header line.
      `);
    case 'sequence-name':
    case 'alph-name':
      return(`
	The name of the alphabet symbol.
      `);
    case 'alph-bg':
      return(`
	The frequency of the alphabet symbol as defined by the background model.
      `);
    case 'match-start-seq':
      return(`
	The start position of the ` + extra + `; 1-based sequence coordinates.
      `);
    case 'match-stop-seq':
      return(`
	The end position of the ` + extra + `; 1-based sequence coordinates.
      `);
    case 'match-start-genomic':
      return(`
	The start position of the ` + extra + `; genomic coordinates.
      `);
    case 'match-stop-genomic':
      return(`
	The end position of the ` + extra + `; genomic coordinates.
      `);
    case 'parse-genomic-coord':
      return(`
	` + extra + ` was run with the <code>--parse-genomic-coord</code> option
	and has split the sequence identifier into sequence name, sequence start and sequence end 
	in genomic coordinates.
      `);

    // AME
    case 'ame-pvalue':
      return(`
	The optimal enrichment <i>p</i>-value of the motif according to the statistical test;
	not adjusted for multiple tests.
      `);
    case 'ame-adj-pvalue':
      return(`
	The optimal enrichment <i>p</i>-value of the motif according to the statistical test,
        adjusted for multiple tests using a Bonferroni correction. ` + extra + `
	If the best <i>p</i>-value is <i>p</i> before adjustment,
        and the number of multiple tests is <i>n</i>, then the adjusted
        <i>p</i>-value is 1 - (1-<i>p</i>)<i><sup>n</sup></i>.
      `);
    case 'ame-evalue':
      return(`
	The expected number of motifs that would be as enriched in the
        (primary) sequences as this one.  The <i>E</i>-value is the adjusted <i>p</i>-value
        multiplied by the number of motifs in the motif file(s).
      `);

    // CentriMo
    case 'centrimo-adj-pvalue':
      return(`
        The statistical significance of the enrichment of the motif, adjusted for multiple tests. ` + extra + `
      `);
    case 'centrimo-evalue':
      var evalue_html = `
        at least one region as enriched for best matches to the motif as the reported region
	(or would have optimal average distance to the sequence center as low as observed, 
	if you used the <code>--cd</code> option).
      `;
      return(`
	The expected number motifs that would have ` + (extra ? extra : evalue_html) + `
	The <i>E</i>-value is the adjusted <i>p</i>-value multiplied by the number of motifs in the
	input files(s).</td> </tr>
      `);
    case 'centrimo-bin-width':
      return(`
        The width (in sequence positions) of the most enriched region (default),
        <b>or</b> two times the average distance between the center of the best site
        and the sequence center if you used the option <code>--cd</code>.
        A best match to the motif is counted as being in the region if
        the center of the motif falls in the region.
      `);
    case 'centrimo-sites-in-bin':
      return(`
	The number of (positive) sequences whose best match to the motif `
	+ (extra ? extra : "falls in the reported region (default) or anywhere in the sequence (if you used the option <code>--cd</code>).") + `
	<br>Note: This number may be less than the number of
	(positive) sequences that have a best match in the region.
	The reason for this is that a sequence may have many matches that score
	equally best.  If <i>n</i> matches have the best score in a sequence, 1/<i>n</i> is added to the
	appropriate bin for each match.</td> </tr>
      `);
    case 'centrimo-mult-tests':
      return(`
	This is the number of multiple tests (<i>n</i>) done for this motif.
	It was used to adjust the <i>p</i>-value of a region for
	multiple tests using the formula:
	<br>&nbsp;&nbsp;
	  <i>p</i>' = 1 - (1-<i>p</i>)<sup><i>n</i></sup> where <i>p</i> is the unadjusted <i>p</i>-value.
	<br>
	The number of multiple tests is the number of regions
	considered times the number of score thresholds considered.
	It depends on the motif length, sequence length, and the type of
	optimizations being done (central enrichment, local enrichment, central distance or
	score optimization).</td> </tr>
      `);

    // GOMo
    case 'gomo-go-term':
      return(`
        The Gene Ontology Consortium term for a specific role or locality.
        Used for annotating genes with their functions.
      `);
    case 'gomo-score':
      return( `
	A score generated as the <a href="https://en.wikipedia.org/wiki/Geometric_mean">
	geometric mean</a> of <a href="https://en.wikipedia.org/wiki/Mann-Whitney_U_test">rank-sum test(s)</a> 
	for the particular Gene Ontology term. The two groups compared by the rank-sum test are scores of genes annotated 
	with the GO term and scores of genes not annotated with the GO term.</td> </tr>
      `);
    case 'gomo-p-value':
      return( `
	An empirically generated <i>p</i>-value for the enrichment of the GO term.<br>
	The null hypothesis is that by shuffling the labels on gene scores, 
	any possible association between the set of genes that a GO term annotates is destroyed. 
	A large number of scores are generated using the null hypothesis and the number of null 
	hypothesis scores that are better than each of the real scores is summed and then divided 
	by the total null hypothesis scores generated to calculate a <i>p</i>-value.</td> </tr>
      `);

    // MAST

    // MCAST
    case 'mcast-cluster-score':
      return( `
        The score that the hidden Markov model created by MCAST assigned to the motif cluster.<br>
        This is the sum of the scores of the individual motif matches in the cluster, plus a
        gap penalty, <i>g</i>, multiplied by the total size of the inter-motif gaps
        in the cluster.  Individual motif match scores are log2(<i>P(s)/p</i>), where <i>s</i> is the 
        <a href="javascript:help_refine('pop_motif_match_score')">log-odds score</a> 
        of the motif match, <i>P(s)</i> is the 
        <a href="javascript:help_refine('pop_motif_match_pvalue')"><i>p</i>-value</a> 
	of the motif match, and <i>p</i> is the user-specified <i>p</i>-value threshold (default: 0.0005).
        <div class="active" id="pop_motif_match_score_act"></div>
        <div class="active" id="pop_motif_match_pvalue_act"></div>
      `);
    case 'mcast-cluster-p-value':
      return( `
	The <i>p</i>-value of the motif cluster score.<br>
	MCAST estimates <i>p</i>-values by fitting an exponential distribution
	to the observed motif cluster scores.
      `);
    case 'mcast-cluster-E-value':
      return( `
        The <i>E</i>-value of the motif cluster score.<br>
	The <i>E</i>-value is an estimate of the <i>number</i> of false positive matches
	with <i>p</i>-values at least as good as this match\'s.  
        MCAST estimates this by multiplying the motif cluster score <i>p</i>-value
        times the (estimated) number of random matches found in the search.
      `);

    // Tomtom
    case 'tomtom-offset':
      return( `
        The offset of the query motif relative to the target motif in the optimal alignment.<br>
 	A positive value indicates the query is shifted right.
      `);
    case 'tomtom-p-value':
      return( `
        The probability that a random motif of the same width as the target would have an 
	optimal alignment with a match score as good or better than the target's.<br>
	Tomtom estimates the <i>p</i>-value using a null model consisting of sampling
	motif columns from all the columns in the set of target motifs.
      `);
    case 'tomtom-E-value':
      return( `
	The expected number of false positives in the matches up to this point.<br>
	Tomtom estimates the <i>E</i>-value by multiplying the <i>p</i>-value by
	the total number of target motifs in all the target databases.
      `);
    case 'tomtom-overlap':
      return( `
	The number of motif columns that overlap in the optimal alignment.
      `);
    case 'tomtom-orientation':
      return( `
	The orientation of the target motif that gave the optimal alignment. ` + extra + `
      `);
  }

} // get_doc_text

//
// Function to replace the innerHTML of element "id" with an HTML paragraph
// containing the text for 'doc_type', which is known to function get_doc_text.
// This function can be used in help pop-ups.
//
function print_doc_para(id, doc_type, extra) {
  html = `<p>` + get_doc_text(doc_type, extra) + `</p>`; 
  document.getElementById(id).insertAdjacentHTML('beforeend', html);
} // print_doc_para

    </script>
    <script type="text/javascript">
function make_alpha_bg_table(alph, freqs) {
  function colour_symbol(index) {
    var span = document.createElement("span");
    span.appendChild(document.createTextNode(alph.get_symbol(index)));
    span.style.color = alph.get_colour(index);
    span.className = "alpha_symbol";
    return span;
  }
  var table, thead, tbody, row, th, span, i;
  // create table
  table = document.createElement("table");
  table.className = "alpha_bg_table";
  // create header
  thead = document.createElement("thead");
  table.appendChild(thead);
  row = thead.insertRow(thead.rows.length);
  if (alph.has_complement()) {
    add_text_header_cell(row, "Name", "pop_alph_name");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
    add_text_header_cell(row, "");
    add_text_header_cell(row, "");
    add_text_header_cell(row, "");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    add_text_header_cell(row, "Name", "pop_alph_name");
  } else {
    add_text_header_cell(row, "");
    add_text_header_cell(row, "Name", "pop_alph_name");
    if (freqs != null) add_text_header_cell(row, "Freq.", "pop_alph_freq");
    if (alph.has_bg()) add_text_header_cell(row, "Bg.", "pop_alph_bg");
  }
  // add alphabet entries
  tbody = document.createElement("tbody");
  table.appendChild(tbody);
  if (alph.has_complement()) {
    for (i = 0; i < alph.get_size_core(); i++) {
      var c = alph.get_complement(i);
      if (i > c) continue;
      row = tbody.insertRow(tbody.rows.length);
      add_text_cell(row, alph.get_name(i));
      if (freqs != null) add_text_cell(row, "" + freqs[i]);
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(i));
      add_cell(row, colour_symbol(i)); 
      add_text_cell(row, "~");
      add_cell(row, colour_symbol(c)); 
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(c));
      if (freqs != null) add_text_cell(row, "" + freqs[c]);
      add_text_cell(row, alph.get_name(c));
    }
  } else {
    for (i = 0; i < alph.get_size_core(); i++) {
      row = tbody.insertRow(tbody.rows.length);
      add_cell(row, colour_symbol(i)); 
      add_text_cell(row, alph.get_name(i));
      if (freqs != null) add_text_cell(row, "" + freqs[i]);
      if (alph.has_bg()) add_text_cell(row, "" + alph.get_bg_freq(i));
    }
  }
  return table;
}


    </script>
    <script type="text/javascript">
"use strict";

var SpamoConst1 = {
  "size_title" : 18,
  "font_title" : "18px Helvetica",
  "size_subtitle" : 14,
  "font_subtitle" : "14px Helvetica",
  //font for the axis descriptions
  "size_axis" : 12,
  "font_axis" : "12px Helvetica",
  //font for the numbers on the axis
  "size_label" : 9,
  "font_label" : "9px Helvetica",
  "size_tic" : 2,
  "spacer_tic" : 2,
  "spacer_side" : 5,
  "spacer_top" : 5,
  "spacer_bottom" : 5,
  "spacer_title" : 0,
  "spacer_subtitle" : 5,
  "spacer_axis" : 0,
  "spacer_base" : 3,
  "spacer_mid" : 10,
  "spacer_border" : 5,
  "y_axis_label_start" : "Number of Occurrences (total = ",
  "y_axis_label_end" : ")",
  "x_axis_label" : "Distance from Primary to Secondary Motif (gap)",
  "same_strand_label" : "Same Strand",
  "oppo_strand_label" : "Opposite Strand",
  "upstream_label" : "Upstream",
  "downstream_label" : "Downstream",
};


/*
 * Measures the character heights in string
 * by painting them over the top of each other and finding the
 * bounding box. Returns the height of the total string
 * as well as calculating the vertical centering offset.
 */
function string_height(text, font, size) {
  var i, x, y, box;
  if (typeof text === "undefined") throw new Error("Text is not set!");
  if (typeof font === "undefined") throw new Error("Font is not set!");
  if (typeof size === "undefined") throw new Error("Font size is not set!");
  x = size / 2;
  y = size + (size / 2);
  if (!string_height.box) string_height.box = document.createElement("canvas");
  string_height.box.width = size * 2;
  string_height.box.height = size * 2;
  var ctx = string_height.box.getContext('2d');
  ctx.font = font;
  for (i = 0; i < text.length; ++i) {
    ctx.fillText(text.charAt(i), x, y);
  }
  box = bbox(ctx, size * 2, size * 2, true);

  return {height: box.height, vcenteroffset: (box.height / 2)}
}

/*
 * Calculates a tight bounding box on the contents of a canvas
 */
function bbox(ctx, cwidth, cheight, skip_width_calculation, skip_height_calculation) {
  if (typeof skip_width_calculation != 'boolean') skip_width_calculation = false;
  if (typeof skip_height_calculation != 'boolean') skip_height_calculation = false;
  if (skip_width_calculation && skip_height_calculation) throw new Error("bbox: can't skip calculation of both width and height");
  var data = ctx.getImageData(0, 0, cwidth, cheight).data;
  var r = 0, c = 0;// r: row, c: column
  var top_line = -1, bottom_line = -1, left_line = -1, right_line = -1;
  var box_width = 0, box_height = 0;
  if (!skip_height_calculation) {
    // Find the top-most line with a non-white pixel
    for (r = 0; r < cheight; r++) {
      for (c = 0; c < cwidth; c++) {
        if (data[r * cwidth * 4 + c * 4 + 3]) {
          top_line = r;
          break;
        }
      }
      if (top_line != -1) break;
    }
    
    //find the last line with a non-white pixel
    if (top_line != -1) {
      for (r = cheight-1; r >= top_line; r--) {
        for(c = 0; c < cwidth; c++) {
          if(data[r * cwidth * 4 + c * 4 + 3]) {
            bottom_line = r;
            break;
          }
        }
        if (bottom_line != -1) break;
      }
      box_height = bottom_line - top_line + 1;
    }
  }

  if (!skip_width_calculation) {
    // Find the left-most line with a non-white pixel
    for (c = 0; c < cwidth; c++) {
      for (r = 0; r < cheight; r++) {
        if (data[r * cwidth * 4 + c * 4 + 3]) {
          left_line = c;
          break;
        }
      }
      if (left_line != -1) break;
    }

    //find the right most line with a non-white pixel
    if (left_line != -1) {
      for (c = cwidth-1; c >= left_line; c--) {
        for(r = 0; r < cheight; r++) {
          if(data[r * cwidth * 4 + c * 4 + 3]) {
            right_line = c;
            break;
          }
        }
        if (right_line != -1) break;
      }
      box_width = right_line - left_line + 1;
    }
  }
  //return the bounds
  if (!skip_height_calculation && !skip_width_calculation) {
    return {top: top_line, bottom: bottom_line, left: left_line, right: right_line, width: box_width, height: box_height};
  } else if (!skip_height_calculation) {
    return {top: top_line, bottom: bottom_line, height: box_height};
  } else {
    return {left: left_line, right: right_line, width: box_width};
  }
}

var SpamoQuadGraphMetrics = function(spamo, target_width, target_height, ctx) {
  var i;

  this.width = target_width;
  this.height = target_height;

  this.revcomp = (spamo.bins.length == 4);

  //count axis numbers width
  this.count_width = 0;
  ctx.font = SpamoConst1.font_label;
  for (i = 0; i <= spamo.y_axis_max; i+= 5) {
    var width = ctx.measureText("" + i).width;
    if (width > this.count_width) {
      this.count_width = width;
    }
  }
  //spacing axis numbers width
  this.spacing_width = 0;
  ctx.font = SpamoConst1.font_label;
  for (i = -spamo.graph_margin; i <= spamo.graph_margin; ++i) {
    var width = ctx.measureText("" + i).width;
    if (width > this.spacing_width) {
      this.spacing_width = width;
    }
  }
  //calculate the height of a quadrant
  this.quad_height = this.height;
  if (spamo.graph_has_border) {
    this.quad_height -= 2 * SpamoConst1.spacer_border;
  }
  this.quad_height -= SpamoConst1.spacer_top;
  if (spamo.graph_has_title) {
    this.quad_height -= SpamoConst1.size_title;
  }
  if (spamo.graph_has_title && spamo.graph_has_subtitle) {
    this.quad_height -= SpamoConst1.spacer_title;
  }
  if (spamo.graph_has_subtitle) {
    this.quad_height -= SpamoConst1.size_subtitle;
  }
  if (spamo.graph_has_title || spamo.graph_has_subtitle) {
    this.quad_height -= SpamoConst1.spacer_subtitle;
  }
  if (spamo.graph_has_stream_label) {
    this.quad_height -= SpamoConst1.size_axis;
  }
  this.quad_height -= SpamoConst1.spacer_base * (this.revcomp ? 2 : 1);
  this.quad_height -= this.spacing_width;
  if (spamo.graph_has_x_axis_label) {
    this.quad_height -= SpamoConst1.spacer_axis + SpamoConst1.size_axis;
  }
  this.quad_height -= SpamoConst1.spacer_bottom;
  if (this.revcomp) this.quad_height /= 2;
  this.quad_height = Math.floor(this.quad_height);

  //calculate the max width of a quadrant
  this.quad_max_width = this.width;
  if (spamo.graph_has_border) {
    this.quad_max_width -= 2 * SpamoConst1.spacer_border;
  }
  this.quad_max_width -= 2 * SpamoConst1.spacer_side;
  if (spamo.graph_has_y_axis_label) {
    this.quad_max_width -= SpamoConst1.size_axis + SpamoConst1.spacer_axis; 
  }
  this.quad_max_width -= this.count_width + SpamoConst1.spacer_tic + SpamoConst1.spacer_mid;
  if (spamo.graph_has_strand_label) {
    this.quad_max_width -= SpamoConst1.size_axis + SpamoConst1.spacer_axis;
  }
  this.quad_max_width /= 2;
  this.quad_max_width = Math.floor(this.quad_max_width);

  //calculate the width of a graph bar the size of a single unit
  this.unit_width = this.quad_max_width / spamo.graph_margin;
  if (this.unit_width > 1) this.unit_width = Math.floor(this.unit_width);

  //calculate the width of the bar which represents the bin furtherest from the
  //primary which may be smaller than the other bars because the motif width
  //does not allow 
  this.partial_bar_width = ((spamo.graph_margin - spamo.graph_mlength + 1) % spamo.graph_binsize) * this.unit_width;

  //calculate the width of a full bar
  this.full_bar_width = this.unit_width * spamo.graph_binsize;

  //calculate the true width of a quadrant
  this.quad_width = this.unit_width * spamo.graph_margin;

  //add the excess onto the central spacer
  this.spacer_mid = SpamoConst1.spacer_mid + (this.quad_max_width - this.quad_width) * 2;

  //calculate the left quad
  this.left_quad = 0;
  if (spamo.graph_has_border) {
    this.left_quad += SpamoConst1.spacer_border;
  }
  this.left_quad += SpamoConst1.spacer_side;
  if (spamo.graph_has_y_axis_label) {
    this.left_quad += SpamoConst1.size_axis + SpamoConst1.spacer_axis;
  }
  this.left_quad += this.count_width + SpamoConst1.spacer_tic + SpamoConst1.size_tic;

  //calculate the right quad
  this.right_quad = this.left_quad + this.quad_width + this.spacer_mid;

  //calculate the bottom quad
  this.bottom_quad = 0;
  if (spamo.graph_has_border) {
    this.bottom_quad += SpamoConst1.spacer_border;
  }
  this.bottom_quad += SpamoConst1.spacer_bottom;
  if (spamo.graph_has_x_axis_label) {
    this.bottom_quad += SpamoConst1.size_axis + SpamoConst1.spacer_axis;
  }

  //calculate the top quad
  if (this.revcomp) {
    this.top_quad = this.bottom_quad + this.quad_height + (2 * SpamoConst1.spacer_base) + this.spacing_width;
  } else {
    this.top_quad = this.bottom_quad + this.spacing_width +  SpamoConst1.spacer_base;
  }
};

var SpamoQuadGraph = function (margin, bin_size, bin_max_count, secondary_motif_length, counts, highlights) {
  "use strict";
  var add = function (a, b) { return a + b; }
  //initialise graph state to default
  this.graph_title = "";
  this.graph_margin = margin;
  this.graph_mlength = secondary_motif_length
  this.graph_binsize = bin_size; 
  this.graph_binmax = bin_max_count;
  this.graph_sequences = counts.map(function(list) { return list.reduce(add); } ).reduce(add);
  this.graph_significant = 0.05;
  this.graph_has_border = false;
  this.graph_has_subtitle = false;
  this.graph_has_y_axis_label = true;
  this.graph_has_x_axis_label = true;
  this.graph_has_stream_label = false;
  this.graph_has_strand_label = false;

  //calculate bin count to read in the bin values
  var possible_count = this.graph_margin - this.graph_mlength + 1;
  var bin_count = Math.floor(possible_count / this.graph_binsize) + (possible_count % this.graph_binsize > 0 ? 1 : 0);
  var i;

  //get the bins
  this.bins = counts;
  this.highlights = highlights;

  //calculate variables
  this.graph_has_title = this.graph_title.length > 0;
  this.graph_subtitle = "margin: " + this.graph_margin + "   bin size: " + this.graph_binsize;
  this.y_axis_max = (Math.floor(this.graph_binmax / 5) + 1) * 5;
  this.full_bin_count = Math.floor((this.graph_margin - this.graph_mlength + 1) / this.graph_binsize);
};

SpamoQuadGraph.OPPOSITE_RIGHT = 3;
SpamoQuadGraph.OPPOSITE_LEFT = 2;
SpamoQuadGraph.SAME_RIGHT = 1;
SpamoQuadGraph.SAME_LEFT = 0;


SpamoQuadGraph.prototype.draw_border = function(metrics, ctx) {
  var bdr = SpamoConst1.spacer_border;
  var bdr2 = 2 * bdr;
  //add on 0.5 so the rectangle is in the center of a pixel and so is only 1 pixel wide
  ctx.strokeRect(bdr + 0.5, bdr + 0.5, metrics.width - bdr2, metrics.height - bdr2); 
};

SpamoQuadGraph.prototype.draw_title = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_title;
  var title_width = ctx.measureText(this.graph_title).width;
  var x = (metrics.width / 2) - (title_width / 2);
  var y = metrics.height - SpamoConst1.spacer_top - SpamoConst1.size_title;
  if (this.graph_has_border) {
    y -= SpamoConst1.spacer_border;
  }
  ctx.fillText(this.graph_title, x, metrics.height - y);
};

SpamoQuadGraph.prototype.draw_subtitle = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_subtitle;
  var subtitle_width = ctx.measureText(this.graph_subtitle).width;
  var x = (metrics.width / 2) - (subtitle_width / 2);
  var y = metrics.height - SpamoConst1.spacer_top - SpamoConst1.size_subtitle;
  if (this.graph_has_border) {
    y -= SpamoConst1.spacer_border;
  }
  if (this.graph_has_title) {
    y -= (SpamoConst1.size_title + SpamoConst1.spacer_title);
  }
  ctx.fillText(this.graph_subtitle, x, metrics.height - y);
};

SpamoQuadGraph.prototype.draw_y_axis_label = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_axis;
  var txt = SpamoConst1.y_axis_label_start + this.graph_sequences + SpamoConst1.y_axis_label_end;
  var axislabel_width = ctx.measureText(txt).width;
  var x = SpamoConst1.spacer_side + SpamoConst1.size_axis;
  if (this.graph_has_border) {
    x += SpamoConst1.spacer_border;
  }
  var y = (metrics.height / 2) - (axislabel_width / 2);
  ctx.save();
  ctx.translate(x, metrics.height - y);
  ctx.rotate(-(Math.PI / 2));
  ctx.fillText(txt, 0, 0);
  ctx.restore();
};

SpamoQuadGraph.prototype.draw_x_axis_label = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_axis;
  var axislabel_width = ctx.measureText(SpamoConst1.x_axis_label).width;
  var x = (metrics.width / 2) - (axislabel_width / 2);
  var y = SpamoConst1.spacer_bottom;
  if (this.graph_has_border) {
    y += SpamoConst1.spacer_border;
  }
  ctx.fillText(SpamoConst1.x_axis_label, x, metrics.height - y);
};

SpamoQuadGraph.prototype.draw_stream_labels = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_axis;
  var upstream_width = ctx.measureText(SpamoConst1.upstream_label).width;
  var downstream_width = ctx.measureText(SpamoConst1.downstream_label).width;
  var y = metrics.top_quad + metrics.quad_height;
  var x = metrics.left_quad + (metrics.quad_width / 2) - (upstream_width / 2);
  ctx.fillText(SpamoConst1.upstream_label, x, metrics.height - y);
  x = metrics.right_quad + (metrics.quad_width / 2) - (downstream_width / 2);
  ctx.fillText(SpamoConst1.downstream_label, x, metrics.height - y);
};

SpamoQuadGraph.prototype.draw_strand_labels = function(metrics, ctx) {
  ctx.font = SpamoConst1.font_axis;
  var same_strand_width = ctx.measureText(SpamoConst1.same_strand_label).width;
  var x = metrics.right_quad + metrics.quad_width + SpamoConst1.spacer_axis + SpamoConst1.size_axis;
  var y = metrics.top_quad + (metrics.quad_height / 2) - (same_strand_width / 2);
  ctx.save();
  ctx.translate(x, metrics.height - y);
  ctx.rotate(-(Math.PI / 2));
  ctx.fillText(SpamoConst1.same_strand_label, 0, 0);
  ctx.restore();
  if (metrics.revcomp) {
    var oppo_strand_width = ctx.measureText(SpamoConst1.oppo_strand_label).width;
    y = metrics.bottom_quad + (metrics.quad_height / 2) - (oppo_strand_width / 2);
    ctx.save();
    ctx.translate(x, metrics.height - y);
    ctx.rotate(-(Math.PI / 2));
    ctx.fillText(SpamoConst1.oppo_strand_label, 0, 0);
    ctx.restore();
  }
};

SpamoQuadGraph.prototype.draw_y_axis_part = function(metrics, ctx, start_y, height) {
  ctx.font = SpamoConst1.font_label;
  ctx.beginPath();
  ctx.moveTo(metrics.left_quad - 0.5, metrics.height - start_y);
  ctx.lineTo(metrics.left_quad - 0.5, metrics.height - (start_y + height));
  ctx.stroke();
  //y_inc = -(height / this.y_axis_max * 5);
  var n_y_tics = 10
  var i_inc = Math.max(1, Math.floor(this.y_axis_max/n_y_tics));
  var y_inc = -(height / this.y_axis_max * i_inc);
  ctx.save();
  ctx.translate(metrics.left_quad, metrics.height - start_y);
  //for (i = 0; i <= this.y_axis_max; i += 5) {
  for (var i = 0; i <= this.y_axis_max; i += i_inc) {
    var lbl = "" + i;
    var lbl_width = ctx.measureText(lbl).width;
    var lbl_heightbox = string_height(lbl, SpamoConst1.font_label, SpamoConst1.size_label);
    var x = -(lbl_width + SpamoConst1.size_tic);
    var y = lbl_heightbox.vcenteroffset;
    //draw text
    ctx.fillText(lbl, x, y);

    //draw tic
    ctx.beginPath();
    ctx.moveTo(0, 0.5);
    ctx.lineTo(-SpamoConst1.size_tic, 0.5);
    ctx.stroke();

    ctx.translate(0, y_inc);
  }
  ctx.restore();
};

SpamoQuadGraph.prototype.draw_y_axis = function(metrics, ctx) {
  this.draw_y_axis_part(metrics, ctx, metrics.top_quad, metrics.quad_height);  
  if (metrics.revcomp) {
    this.draw_y_axis_part(metrics, ctx, metrics.bottom_quad + metrics.quad_height, -metrics.quad_height);  
  }
};

SpamoQuadGraph.prototype.draw_divider_part = function(metrics, ctx, x, y, len, dir) {
  var offset = 0;
  while (offset <= len) {
    ctx.fillRect(x, y + (offset * dir), 1, 2 * dir);
    offset += 3;
  }
};

SpamoQuadGraph.prototype.draw_divider = function(metrics, ctx) {
  this.draw_divider_part(metrics, ctx, metrics.left_quad + metrics.quad_width, 
      metrics.height - metrics.top_quad, metrics.quad_height, -1);

  this.draw_divider_part(metrics, ctx, metrics.right_quad - 1, 
      metrics.height - metrics.top_quad, metrics.quad_height, -1);

  if (metrics.revcomp) {
    this.draw_divider_part(metrics, ctx, metrics.left_quad + metrics.quad_width, 
        metrics.height - (metrics.bottom_quad + metrics.quad_height), metrics.quad_height, 1);

    this.draw_divider_part(metrics, ctx, metrics.right_quad - 1, 
        metrics.height - (metrics.bottom_quad + metrics.quad_height), metrics.quad_height, 1);
  }
};

SpamoQuadGraph.prototype.draw_motif_boundary = function(metrics, ctx) {
  if (this.graph_mlength == 1) return;
  var offset = metrics.unit_width * (this.graph_mlength - 1);

  this.draw_divider_part(metrics, ctx, metrics.left_quad + offset - 1,
      metrics.height - metrics.top_quad, metrics.quad_height, -1);

  this.draw_divider_part(metrics, ctx, metrics.right_quad + metrics.quad_width - offset, 
      metrics.height - metrics.top_quad, metrics.quad_height, -1);

  if (metrics.revcomp) {
    this.draw_divider_part(metrics, ctx, metrics.left_quad + offset - 1, 
        metrics.height - (metrics.bottom_quad + metrics.quad_height), metrics.quad_height, 1);

    this.draw_divider_part(metrics, ctx, metrics.right_quad + metrics.quad_width - offset, 
        metrics.height - (metrics.bottom_quad + metrics.quad_height), metrics.quad_height, 1);
  }
};

SpamoQuadGraph.prototype.draw_ender = function(metrics, ctx) {
  var left = metrics.right_quad + metrics.quad_width + 0.5;
  ctx.save();
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(left, metrics.height - metrics.top_quad);
  ctx.lineTo(left, metrics.height - (metrics.top_quad + metrics.quad_height));
  ctx.stroke();

  if (metrics.revcomp) {
    ctx.beginPath();
    ctx.moveTo(left, metrics.height - (metrics.bottom_quad + metrics.quad_height));
    ctx.lineTo(left, metrics.height - metrics.bottom_quad);
    ctx.stroke();
  }

  ctx.restore();
};

SpamoQuadGraph.prototype.draw_x_axis_num = function(metrics, ctx, num) {
  ctx.font = SpamoConst1.font_label;
  var txt = "" + num;
  var width = ctx.measureText(txt).width;
  var vcenter = string_height(txt, SpamoConst1.font_label, SpamoConst1.size_label).vcenteroffset;
  ctx.save();
  ctx.rotate(-(Math.PI / 2));
  ctx.fillText(txt, -width - 0.5, vcenter);
  ctx.restore();
};

SpamoQuadGraph.prototype.draw_x_axis = function(metrics, ctx) {
  var x = metrics.left_quad + metrics.quad_width;
  var y = metrics.top_quad - SpamoConst1.spacer_base;
  var skip = Math.ceil(SpamoConst1.size_label / metrics.unit_width);
  if (skip == 0) skip = 1;
  if (skip % 10 > 0) {
    skip -= (skip % 10) + 10;
  }
  ctx.save();
  ctx.translate(x - 0.5, metrics.height - y);
  for (var num = 0; num < this.graph_margin; num++) {
    if (num % skip == 0) {
      this.draw_x_axis_num(metrics, ctx, num);
    }
    ctx.translate(-metrics.unit_width, 0);
  }
  ctx.restore();
  x = metrics.right_quad;
  ctx.save();
  ctx.translate(x + 0.5, metrics.height - y);
  for (var num = 0; num < this.graph_margin; num++) {
    if (num % skip == 0) {
      this.draw_x_axis_num(metrics, ctx, num);
    }
    ctx.translate(metrics.unit_width, 0);
  }
  ctx.restore();

};

SpamoQuadGraph.prototype.draw_bar = function(metrics, ctx, bar_width, bin, highlight) {
  var bar_height;
  if (typeof bin !== "number" || bin == 0) return;
  bar_height = (bin / this.y_axis_max) * metrics.quad_height;
  ctx.fillStyle = (typeof highlight === "string" ? highlight : "rgb(205, 205, 205)");
  ctx.fillRect(0, 0, bar_width, -bar_height);
};

SpamoQuadGraph.prototype.draw_bars_part = function(metrics, ctx, bins, highlights) {
  var i;
  //skip unused
  if (this.graph_mlength > 1) ctx.translate(metrics.unit_width * (this.graph_mlength - 1), 0);
  i = Math.ceil((this.graph_margin - this.graph_mlength + 1) / this.graph_binsize) - 1;
  //draw partial bar
  if (metrics.partial_bar_width > 0) {
    this.draw_bar(metrics, ctx, metrics.partial_bar_width, bins[i], highlights[i]);
    ctx.translate(metrics.partial_bar_width, 0);
    i--;
  }
  //draw full bar
  for (; i >= 0; i--) {
    this.draw_bar(metrics, ctx, metrics.full_bar_width, bins[i], highlights[i]);
    ctx.translate(metrics.full_bar_width, 0);
  }
};

SpamoQuadGraph.prototype.draw_bars = function(metrics, ctx) {
  if (metrics.revcomp) {
    // bottom right
    ctx.save();
    ctx.translate(metrics.right_quad + metrics.quad_width, metrics.height - (metrics.bottom_quad + metrics.quad_height));
    ctx.scale(-1, -1);
    this.draw_bars_part(metrics, ctx, this.bins[SpamoQuadGraph.OPPOSITE_RIGHT], this.highlights[SpamoQuadGraph.OPPOSITE_RIGHT]);
    ctx.restore();
    // bottom left
    ctx.save();
    ctx.translate(metrics.left_quad, metrics.height - (metrics.bottom_quad + metrics.quad_height));
    ctx.scale(1, -1);
    this.draw_bars_part(metrics, ctx, this.bins[SpamoQuadGraph.OPPOSITE_LEFT], this.highlights[SpamoQuadGraph.OPPOSITE_LEFT]);
    ctx.restore();
  }
  // top right
  ctx.save();
  ctx.translate(metrics.right_quad + metrics.quad_width, metrics.height - metrics.top_quad);
  ctx.scale(-1, 1);
  this.draw_bars_part(metrics, ctx, this.bins[SpamoQuadGraph.SAME_RIGHT], this.highlights[SpamoQuadGraph.SAME_RIGHT]);
  ctx.restore();
  // top left
  ctx.save();
  ctx.translate(metrics.left_quad, metrics.height - metrics.top_quad);
  this.draw_bars_part(metrics, ctx, this.bins[SpamoQuadGraph.SAME_LEFT], this.highlights[SpamoQuadGraph.SAME_LEFT]);
  ctx.restore();
};

SpamoQuadGraph.prototype.draw_graph = function(ctx, w, h) {
  var metrics = new SpamoQuadGraphMetrics(this, w, h, ctx);
  
  if (this.graph_has_border) this.draw_border(metrics, ctx);
  if (this.graph_has_title) this.draw_title(metrics, ctx);
  if (this.graph_has_subtitle) this.draw_subtitle(metrics, ctx);
  if (this.graph_has_y_axis_label) this.draw_y_axis_label(metrics, ctx);
  if (this.graph_has_x_axis_label) this.draw_x_axis_label(metrics, ctx);
  if (this.graph_has_strand_label) this.draw_strand_labels(metrics, ctx);
  if (this.graph_has_stream_label) this.draw_stream_labels(metrics, ctx);
  this.draw_y_axis(metrics, ctx);
  this.draw_x_axis(metrics, ctx);
  this.draw_bars(metrics, ctx);
  this.draw_ender(metrics, ctx);
  this.draw_divider(metrics, ctx);
  this.draw_motif_boundary(metrics, ctx);
};

var SpamoConst2 = {
  "padding": 5,
  "xaxis_fontsize": 9,
  "xaxis_font": "9px Helvetica",
  "xaxis_fontcolour": "black",
  "xaxis_spacer": 2,
  "xlabel_fontsize": 12,
  "xlabel_font": "12px Helvetica",
  "xlabel_fontcolour": "black",
  "xlabel_spacer": 2,
  "yaxis_spacer": 1,
  "yaxis_tic": 3,
  "yaxis_ticspacer": 1,
  "yaxis_fontsize": 9,
  "yaxis_fontcolour": "black",
  "yaxis_font": "9px Helvetica",
  "yaxis_fontspacer": 4,
  "ylabel_fontsize": 12,
  "ylabel_fontcolour": "black",
  "ylabel_font": "12px Helvetica",
  "ylabel_spacer": 2,
  "bar_spacer": 1,
  "bar_colour": "rgb(205, 205, 205)"
};

var SpamoOrientGraphMetrics = function(graph, ctx, w, h) {
  "use strict";
  var i, txt_width;
  this.y_min = Math.floor(Math.max(graph.min_count - 1, 0) / 5) * 5;
  this.y_max = Math.ceil((graph.max_count + 1) / 5) * 5;
  // determine maximum width of y-axis text
  this.yaxis_width = 0;
  for (i = this.y_min; i <= this.y_max; i += 5) {
    txt_width = ctx.measureText("" + i).width;
    if (txt_width > this.yaxis_width) this.yaxis_width = txt_width;
  }

  this.width = w;
  this.height = h;

  this.graph_max_width = this.width - (SpamoConst2.padding * 2) -
    SpamoConst2.ylabel_fontsize - SpamoConst2.ylabel_spacer - this.yaxis_width -
    SpamoConst2.yaxis_ticspacer - SpamoConst2.yaxis_tic - SpamoConst2.yaxis_spacer;
  this.graph_max_height = this.height - (SpamoConst2.padding * 2) - 
    (SpamoConst2.yaxis_fontsize / 2) - SpamoConst2.xaxis_spacer - 
    SpamoConst2.xaxis_fontsize - SpamoConst2.xlabel_spacer -
    SpamoConst2.xlabel_fontsize;
  this.bar_width = Math.floor((this.graph_max_width / graph.range) - SpamoConst2.bar_spacer);
  this.bar_height = this.graph_max_height;
  this.graph_height = this.bar_height;
  this.graph_width = (this.bar_width + SpamoConst2.bar_spacer) * graph.range;
  // determine how many units the increment should be based on the font size and spacer size
  var y_units = this.y_max - this.y_min;
  var y_unit_height = this.bar_height / y_units;
  var y_font_units = (SpamoConst2.yaxis_fontsize + SpamoConst2.yaxis_fontspacer) / y_unit_height;
  this.y_inc = Math.ceil(y_font_units / 5) * 5; 
};

var SpamoOrientGraph = function(quadrant_counts, quadrant_list, start, range, bin_size, highlights) {
  "use strict";
  var i, j;
  var MAX_INT = 9007199254740992;
  // store the inputs
  this.hl = highlights;
  this.start = start; // starting bin
  this.range = range; // number of bins
  this.bin_size = bin_size; // size of bin
  // create the summed counts list
  this.counts = [];
  for (i = 0; i < range; i++) this.counts[i] = 0;
  for (i = 0; i < quadrant_list.length; i++) {
    for (j = 0; j < range; j++) {
      this.counts[j] += quadrant_counts[quadrant_list[i]][j + start];
    }
  }
  // calculate the minimum and maximum count
  this.max_count = 0;
  this.min_count = MAX_INT;
  for (i = 0; i < this.counts.length; i++) {
    if (this.counts[i] > this.max_count) this.max_count = this.counts[i];
    if (this.counts[i] < this.min_count) this.min_count = this.counts[i];
  }
};

SpamoOrientGraph.prototype.draw_bars = function(ctx, metrics) {
  var i, bar_height;
  ctx.save();
  for (i = 0; i < this.counts.length; i++) {
    bar_height = Math.round(((this.counts[i] - metrics.y_min) / (metrics.y_max - metrics.y_min)) * metrics.bar_height);
    ctx.fillStyle = (typeof this.hl[this.start + i] === "string" ? this.hl[this.start + i] : SpamoConst2.bar_colour);
    ctx.fillRect(0, 0, metrics.bar_width, -bar_height);
    ctx.translate(metrics.bar_width + SpamoConst2.bar_spacer, 0);
  }
  ctx.restore();
};

SpamoOrientGraph.prototype.draw_x_nums = function(ctx, metrics) {
  var i;
  ctx.save();
  ctx.font = SpamoConst2.xaxis_font;
  ctx.fillStyle = SpamoConst2.xaxis_fontcolour;
  ctx.textAlign = "center";
  ctx.textBaseline = "top";
  for (i = 0; i < this.counts.length; i++) {
    var col_text = "";
    if (this.bin_size == 1) {
      col_text = "" + (this.start + i);
    } else {
      var bin_start = (this.start + i) * this.bin_size;
      var bin_end = bin_start + this.bin_size - 1;
      col_text = "" + bin_start  + ":" + bin_end;
    }
    ctx.fillText(col_text, metrics.bar_width / 2, 0); 
    ctx.translate(metrics.bar_width + SpamoConst2.bar_spacer, 0);
  }
  ctx.restore();
};

SpamoOrientGraph.prototype.draw_y_nums = function(ctx, metrics) {
  var i, num, inc, y_pos;
  ctx.save();
  ctx.font = SpamoConst2.yaxis_font;
  ctx.fillStyle = SpamoConst2.yaxis_fontcolour;
  ctx.textAlign = "right";
  ctx.textBaseline = "middle";
  ctx.lineWidth = 1;
  num = metrics.y_min;
  for (num = metrics.y_min; num <= metrics.y_max; num += metrics.y_inc) {
    y_pos = -( (num - metrics.y_min) / (metrics.y_max - metrics.y_min) ) * metrics.bar_height;
    ctx.fillText("" + num, 0, y_pos);
    ctx.beginPath();
    ctx.moveTo(SpamoConst2.yaxis_ticspacer, y_pos);
    ctx.lineTo(SpamoConst2.yaxis_ticspacer + SpamoConst2.yaxis_tic, y_pos);
    ctx.stroke();
  }
  ctx.fillRect(Math.round(SpamoConst2.yaxis_ticspacer + SpamoConst2.yaxis_tic) - 1, 0, 1, -metrics.bar_height);
  ctx.restore();
};

SpamoOrientGraph.prototype.draw_x_label = function(ctx, metrics) {
  ctx.save();
  ctx.font = SpamoConst2.xlabel_font;
  ctx.fillStyle = SpamoConst2.xlabel_fontcolour;
  ctx.textAlign = "center";
  ctx.textBaseline = "top";
  ctx.fillText("Distance from Primary to Secondary Motif (gap)", metrics.graph_width / 2, 0);
  ctx.restore();
};

SpamoOrientGraph.prototype.draw_y_label = function(ctx, metrics) {
  ctx.save();
  ctx.font = SpamoConst2.ylabel_font;
  ctx.fillStyle = SpamoConst2.ylabel_fontcolour;
  ctx.textAlign = "center";
  ctx.translate(0, metrics.graph_height / 2);
  ctx.rotate(-(Math.PI / 2));
  ctx.fillText("Number of Occurrences", 0, 0);
  ctx.restore();
};

SpamoOrientGraph.prototype.draw_graph = function(ctx, w, h) {
  "use strict";
  var metrics = new SpamoOrientGraphMetrics(this, ctx, w, h);
  ctx.save();
  ctx.translate(SpamoConst2.padding + SpamoConst2.ylabel_fontsize,
      SpamoConst2.padding + (SpamoConst2.yaxis_fontsize / 2));
  this.draw_y_label(ctx, metrics);
  ctx.translate(SpamoConst2.ylabel_spacer + metrics.yaxis_width,
       metrics.bar_height);
  this.draw_y_nums(ctx, metrics);
  ctx.translate(SpamoConst2.yaxis_ticspacer + SpamoConst2.yaxis_tic + SpamoConst2.yaxis_spacer, 0);
  this.draw_bars(ctx, metrics);
  ctx.save();
  ctx.translate(metrics.graph_width, 0);
  ctx.fillRect(0, 0, 1, -metrics.graph_height);
  ctx.restore();
  ctx.translate(0, SpamoConst2.xaxis_spacer);
  this.draw_x_nums(ctx, metrics);
  ctx.translate(0, SpamoConst2.xaxis_fontsize + SpamoConst2.xlabel_spacer);
  this.draw_x_label(ctx, metrics);
  ctx.restore();
};

    </script>
    <script type="text/javascript">
"use strict";

var MAX_INT = 9007199254740992;
var spamo_alphabet = new Alphabet(data.alphabet, data.background);
var orient_list = [[0],[1],[2],[3],[0, 2], [0, 3], [2, 1], [1, 3], [0, 2, 1, 3]];
var orient_names = ["up+", "dn+", "up-", "dn-", 
    "up+/up-", "up+/dn-", "up-/dn+", "dn+/dn-", "all"];
var orient_desc = ["upstream / same strand", "downstream / same strand",
    "upstream / opposite strand", "downstream / opposite strand", 
    "upstream / secondary palindromic", "upstream / primary palindromic", 
    "downstream / primary palindromic", "downstream / secondary palindromic", 
    "all / both palindromic"];
if (!spamo_alphabet.has_complement()) {
  orient_names = ["up", "dn"];
  orient_desc = ["upstream", "downstream"];
}

var DelayLogoTask = function(logo, canvas) {
  "use strict";
  canvas.width = canvas.width; // clear canvas
  this.logo = logo;
  this.canvas = canvas;
};

DelayLogoTask.prototype.run = function () {
  "use strict";
  this.canvas.width = this.canvas.width; // clear canvas
  draw_logo_on_canvas(this.logo, this.canvas);
};

function make_logo(canvas, motif) {
  var pspm;
  pspm = new Pspm(motif.pwm, motif.id, motif.ltrim, motif.rtrim, motif.nsites);
  var logo = new Logo(spamo_alphabet);
  logo.add_pspm(pspm, 0);
  canvas.width = 0; // allow any width
  size_logo_on_canvas(logo, canvas);
  add_draw_task(canvas, new DelayLogoTask(logo, canvas));
  return canvas;
}

function make_pwm_logo(canvas, motif_link, eps_link, pwm, dots, name, shift) {
  if (pwm == null) {
    canvas.style.visibility = "hidden";
    if (motif_link) motif_link.style.visibility = "hidden";
    if (eps_link) eps_link.style.visibility = "hidden";
    return 0;
  }
  var pspm;
  pspm = new Pspm(pwm, name);
  var logo = new Logo(spamo_alphabet, {x_axis: !dots, y_axis: !dots});
  logo.add_pspm(pspm, shift);
  if (motif_link) {
    prepare_download(pspm.as_meme({"alphabet": spamo_alphabet, "with_header": true}), "text/plain", (typeof name == "string" ? name.replace(/ /g, "_") + ".meme" : "motif.meme"), motif_link);
    motif_link.style.visibility = "visible";
  }
  canvas.width = 0; // clear canvas
  size_logo_on_canvas(logo, canvas);
  if (eps_link) {
    prepare_download(logo.as_eps(), "application/postscript", (typeof name == "string" ? name.replace(/ /g, "_") + ".eps" : "logo.eps"), eps_link);
    eps_link.style.visibility = "visible"
  }
  canvas.style.visibility = "visible";
  add_draw_task(canvas, new DelayLogoTask(logo, canvas));
  return canvas.width;
}

function make_minimal_spacing_diagram(spacing, start, length) {
  "use strict";
  var i, j, canvas, ctx, entry, colours;
  var w, h, g;
  colours = ["#DDD", "#CCC", "#BBB", "#AAA", "#999", "#888", "#777", "#666",
          "#555", "#444", "#333", "#222", "#111", "#000"];
  w = 1;
  h = 2;
  g = 0;
  canvas = document.createElement("canvas");
  canvas.height = 9 * (h + g);
  canvas.width = length * (w+g);
  ctx = canvas.getContext('2d');
  ctx.fillStyle = "#EEE";
  ctx.fillRect(0, 0, canvas.width, canvas.height); 
  ctx.fillStyle = "#000"
  for (i = 0; i < spacing.length; i++) {
    entry = spacing[i];
    if (entry.bin < start || entry.bin >= (start + length)) continue;
    ctx.fillStyle = (i != 0 ? 
        colours[
        Math.min(
          Math.max(
            0, 
            Math.round(-(Math.log(entry.pvalue)/Math.LN10))),
          colours.length - 1)
        ] :
        "red");
    ctx.fillRect((entry.bin - start) * (w + g), entry.orient * (h + g), w, h);
  }
  return canvas;
}

function make_compact_graph(secondary) {
  var i, j;
  var h = 30;
  var w = data.options.margin;
  var bsp = secondary.spacings[0]; // best spacing
  var counts = [];
  for (i = 0; i < w; i++) counts[i] = 0;
  var orients = orient_list[bsp.orient];
  for (i = 0; i < orients.length; i++) {
    for (j = 0; j < secondary.counts[orients[i]].length; j++) {
      counts[j] += secondary.counts[orients[i]][j];
    }
  }
  var count_max = Math.max.apply(null, counts);
  var spacing_bins = {};
  for (i = 0; i < secondary.spacings.length; i++) {
    if (secondary.spacings[i].orient != bsp.orient) continue;
    spacing_bins[secondary.spacings[i].bin] = true;
  }
  var canvas = document.createElement("canvas");
  canvas.width = w;
  canvas.height = h;
  var ctx = canvas.getContext('2d');
  ctx.fillStyle = "white";
  ctx.fillRect(0, 0, canvas.width, canvas.height); 
  ctx.fillStyle = "black";
  for (i = 0; i < w; i++) {
    if (spacing_bins[i]) continue;
    ctx.fillRect(i, h, 1, -(h * (counts[i] / count_max)));
  }
  ctx.fillStyle = "red";
  for (i = 1; i < secondary.spacings.length; i++) {
    var spacing = secondary.spacings[i];
    if (spacing.orient != bsp.orient) continue;
    ctx.fillRect(spacing.bin, h, 1, -(h * (counts[spacing.bin] / count_max)));
  }
  ctx.fillRect(bsp.bin, h, 1, -(h * (counts[bsp.bin] / count_max)));
  return canvas;
}

function make_alignment_eps(manager) {
  "use strict";
  var i, alignments, logo, pspm;
  alignments = manager.spacing.alignment_pwm;
  logo = new Logo(spamo_alphabet, {x_axis: false, y_axis: false});
  for (i = 0; i < alignments.length; i++) {
    if (alignments[i] != null) {
      pspm = new Pspm(alignments[i], orient_desc[i]);
      logo.add_pspm(pspm, 0);
    }
  }
  var seqs_desc = "alignseqs_" + manager.primary.motif.id + "_with_" +
    manager.secondary.motif.id + "_g" + manager.spacing.bin + "_o" +
    manager.spacing.orient;
  prepare_download(logo.as_eps(), "application/postscript", seqs_desc,
      manager.block.querySelector(".sa_align_eps"));
}

function make_secondary_pair_eps(manager) {
  "use strict";
  var logo, motif, pspm, eps_content;

  logo = new Logo(spamo_alphabet);
  motif = manager.secondary.motif;
  pspm = new Pspm(motif.pwm, motif.id, motif.ltrim, motif.rtrim, motif.nsites);
  logo.add_pspm(pspm, 0);
  pspm = new Pspm(manager.spacing.inferred_pwm);
  logo.add_pspm(pspm, 0);
  eps_content = logo.as_eps({"title": motif.id, "xaxislabel": "Inferred motif"});
  prepare_download(eps_content, "application/postscript", "inferred.eps",
      manager.block.querySelector(".sa_slogo_eps"));
}

function draw_overview_graph(manager) {
  "use strict";
  var canvas, hl, spacings, spacing, orients, i, j, counts, max_count, spamo_graph;
  canvas = manager.block.querySelector(".sa_overview_graph")
  hl = [{},{},{},{}];
  // set pink highlights for all bins involved in a significant spacing
  if (manager.highlight_all) {
    spacings = manager.secondary.spacings;
    for (i = 0; i < spacings.length; i++) {
      spacing = spacings[i];
      orients = orient_list[spacing.orient];
      for (j = 0; j < orients.length; j++) {
        hl[orients[j]][spacing.bin] = "pink";
      }
    }
  }
  // set red highlight for all bins involved in the selected spacing
  if (manager.highlight_selected) {
    orients = orient_list[manager.spacing.orient];
    for (j = 0; j < orients.length; j++) {
      hl[orients[j]][manager.spacing.bin] = "red";
    }
  }

  counts = manager.secondary.counts;
  max_count = Math.max.apply(null, counts.map( function (list) { return Math.max.apply(null, list);} ));
  spamo_graph = new SpamoQuadGraph(data.options.margin, data.options.bin_size, max_count, manager.secondary.motif.len, counts, hl);
  canvas.width = canvas.width;
  var ctx = canvas.getContext('2d');
  var eps_ctx = new EpsContext(ctx, canvas.width, canvas.height);
  eps_ctx.register_font("9px Helvetica", "Helvetica", 9);
  eps_ctx.register_font("12px Helvetica", "Helvetica", 12);
  eps_ctx.register_font("14px Helvetica", "Helvetica", 14);
  eps_ctx.register_font("18px Helvetica", "Helvetica", 18);
  spamo_graph.draw_graph(ctx, canvas.width, canvas.height);
  var filename = "overview_" + manager.primary.motif.id + "_to_" +
      manager.secondary.motif.id + "_g" + manager.spacing.bin + "_o" +
      manager.spacing.orient + ".eps";
  prepare_download(eps_ctx.eps(), "application/postscript", filename,
      manager.block.querySelector(".sa_overview_graph_dl"));
}

function best_window(orient, range, req_bin, max_bin, spacings) {
  var i;
  var left = Math.max(req_bin - range + 1, 0);
  var right = Math.min(req_bin + range - 1, max_bin);
  var size = right - left + 1;
  var starts = [];
  for (i = 0; i < size; i++) starts[i] = 0;
  var peak_i = 0;
  var peak_pvalue = 1;
  for (i = 0; i < spacings.length; i++) {
    var spacing = spacings[i];
    if (spacing.orient == orient && spacing.bin >= left && spacing.bin <= right) {
      starts[spacing.bin - left] = Math.log(spacing.pvalue);
      if (spacing.pvalue < peak_pvalue) {
        peak_pvalue = spacing.pvalue;
        peak_i = spacing.bin - left;
      }
    }
  }
  for (i = 1; i < size; i++) starts[i] += starts[i - 1];
  var best_log_pvalues = 0;
  var best_peak_dist = range;
  var best_i = [];
  for (i = 0; i < (size - range + 1); i++) {
    var log_pvalues = starts[i + range - 1] - starts[i];
    var peak_dist = Math.abs((range / 2) - peak_i + i);
    if (log_pvalues < best_log_pvalues || 
        (log_pvalues == best_log_pvalues && peak_dist < best_peak_dist)) {
      best_log_pvalues = log_pvalues;
      best_peak_dist = peak_dist;
      best_i = [i];
    } else if (log_pvalues == best_log_pvalues && peak_dist == best_peak_dist) {
      best_i.push(i);
    }
  }
  return (best_i.length > 0 ? left + best_i[0] : req_bin - Math.round(range / 2));
}

function draw_sg(manager) {
  "use strict";
  var canvas, ctx;
  var i, hl;
  var min_count, max_count, metrics;
  canvas = manager.block.querySelector(".sa_selected_graph");
  hl = {};
  if (manager.highlight_all) {
    for (i = 0; i < manager.secondary.spacings.length; i++) {
      if (manager.secondary.spacings[i].orient == manager.spacing.orient) {
        hl[manager.secondary.spacings[i].bin] = "pink";
      }
    }
  }
  if (manager.highlight_selected) {
    hl[manager.spacing.bin] = "red";
  }
  var range = 20;
  var max_bin = data.options.margin - manager.secondary.motif.len;
  var start = best_window(manager.spacing.orient, range, manager.spacing.bin, max_bin, manager.secondary.spacings);
  var bin_size = data.options.bin_size;
  var graph = new SpamoOrientGraph(manager.secondary.counts, orient_list[manager.spacing.orient], start, range, bin_size, hl);
  canvas.width = canvas.width; // clear the canvas
  ctx = canvas.getContext('2d');
  var eps_ctx = new EpsContext(ctx, canvas.width, canvas.height);
  eps_ctx.register_font("9px Helvetica", "Helvetica", 9);
  eps_ctx.register_font("12px Helvetica", "Helvetica", 12);
  eps_ctx.register_font("14px Helvetica", "Helvetica", 14);
  eps_ctx.register_font("18px Helvetica", "Helvetica", 18);
  graph.draw_graph(ctx, canvas.width, canvas.height);
  var filename = "selorient_" + manager.primary.motif.id + "_to_" + manager.secondary.motif.id + "_g" + manager.spacing.bin + "_o" + manager.spacing.orient + ".eps"
  prepare_download(eps_ctx.eps(), "application/postscript", filename,
      manager.block.querySelector(".sa_selected_graph_dl"));
}

function make_name_txt(motif, nbsp) {
  var name;
  name = motif.id + (typeof motif.alt === "string" ? (nbsp ? "\u00A0" : " ") + "(" + motif.alt + ")" : "");
  return name;
}

function make_name(motif, nbsp, should_link) {
  var name, link;
  name = motif.id + (typeof motif.alt === "string" ? (nbsp ? "\u00A0" : " ") + "(" + motif.alt + ")" : "");
  if (should_link && typeof motif.url === "string") {
    link = document.createElement("a");
    link.href = motif.url;
    link.appendChild(document.createTextNode(name));
    return link;
  } else {
    return document.createTextNode(name);
  }
}

function make_pri_table() {
  "use strict";
  function _make_list_handler(primaryi, primary, secondary) {
    return function(e) {
      var block, manager;
      // get the block
      block = document.getElementById("primary_" + primaryi);
      if (!block) return;
      // get the manager
      manager = block.data_manager;
      manager.primary = primary;
      manager.secondary = secondary;
      manager.spacing = secondary.spacings[0];
      update_secondaries_list(manager);
      update_selected_secondary(manager);
      block.scrollIntoView();
      e.preventDefault();
    };
  }
  var pri_table, pri_row, pri_tbody;
  var primary, secondaries, secondary;
  var row, row_mlist, mlink;
  var i, j;

  pri_table = $("pri_tbl");
  pri_row = pri_table.querySelector(".pri_row");
  pri_tbody = pri_row.parentNode;
  pri_tbody.removeChild(pri_row);
  for (i = 0; i < data.primaries.length; i++) {
    primary = data.primaries[i].motif;
    secondaries = data.primaries[i].secondaries;
    row = pri_row.cloneNode(true);
    row.querySelector(".pri_db").appendChild(document.createTextNode(data.primary_dbs[primary.db].name));
    row.querySelector(".pri_name").appendChild(make_name(primary, false, true));
    make_logo(row.querySelector(".pri_logo"), primary);
    row.querySelector(".pri_nlist").appendChild(document.createTextNode(secondaries.length));
    row_mlist = row.querySelector(".pri_list");
    for (j = 0; j < secondaries.length; j++) {
      if (j > 0) row_mlist.appendChild(document.createTextNode(",\u2003 "));// EM Space
      secondary = secondaries[j];
      mlink = document.createElement("a");
      mlink.href = "?pdb=" + primary.db + "&pid=" + primary.id + "&sdb=" + secondary.motif.db + "&sid=" + secondary.motif.id;
      mlink.className = (i % 2 == 0 ? "ml1" : "ml2");
      mlink.appendChild(make_name(secondary.motif, true, false));
      mlink.addEventListener("click", _make_list_handler(i, data.primaries[i], secondary), false);
      //mlink.href = "#match_" + i + "_" + secondary.idx;
      row_mlist.appendChild(mlink);
    }
    pri_tbody.appendChild(row);
  }
}

function make_seq_table() {
  "use strict";
  var seq_table, seq_row, seq_tbody;
  var seq_db, used;
  var row;
  var i;

  seq_table = $("seq_tbl");
  seq_row = seq_table.querySelector(".seq_row");
  seq_tbody = seq_row.parentNode;
  seq_tbody.removeChild(seq_row);
  for (i = 0; i < data.sequence_dbs.length; i++) {
    seq_db = data.sequence_dbs[i];
    row = seq_row.cloneNode(true);
    row.querySelector(".seq_name").appendChild(document.createTextNode(seq_db.name));
    row.querySelector(".seq_last_modified").appendChild(document.createTextNode(seq_db.last_modified));
    row.querySelector(".seq_loaded").appendChild(document.createTextNode(seq_db.loaded));
    row.querySelector(".seq_too_short").appendChild(document.createTextNode(seq_db.excluded_too_short));
    row.querySelector(".seq_ambiguous").appendChild(document.createTextNode(seq_db.excluded_ambigs));
    row.querySelector(".seq_no_primary").appendChild(document.createTextNode(seq_db.excluded_no_match));
    row.querySelector(".seq_too_similar").appendChild(document.createTextNode(seq_db.excluded_similar));
    used = seq_db.loaded - seq_db.excluded_too_short - seq_db.excluded_ambigs - seq_db.excluded_no_match - seq_db.excluded_similar;
    row.querySelector(".seq_used").appendChild(document.createTextNode(used));

    seq_tbody.appendChild(row);
  }
}

function make_sdb_table() {
  "use strict";
  var sdb_table, sdb_row, sdb_tbody;
  var sdb_db, smotif, pm_count, rm_count;
  var row;
  var i, j;

  // count how many motifs
  pm_count = [];
  rm_count = [];
  for (i = 0; i < data.secondary_dbs.length; i++) {
    pm_count[i] = 0;
    rm_count[i] = 0;
  }
  for (i = 0; i < data.secondary_motifs.length; i++) {
    smotif = data.secondary_motifs[i];
    if (smotif.nonredundant) {
      pm_count[smotif.db]++;
    } else {
      rm_count[smotif.db]++;
    }
  }

  sdb_table = $("sdb_tbl");
  sdb_row = sdb_table.querySelector(".sdb_row");
  sdb_tbody = sdb_row.parentNode;
  sdb_tbody.removeChild(sdb_row);
  for (i = 0; i < data.secondary_dbs.length; i++) {
    sdb_db = data.secondary_dbs[i];
    row = sdb_row.cloneNode(true);
    row.querySelector(".sdb_name").appendChild(document.createTextNode(sdb_db.name));
    row.querySelector(".sdb_last_modified").appendChild(document.createTextNode(sdb_db.last_modified));
    row.querySelector(".sdb_loaded").appendChild(document.createTextNode(sdb_db.loaded));
    row.querySelector(".sdb_sig_motifs").appendChild(document.createTextNode(pm_count[i]));
    row.querySelector(".sdb_redundant_motifs").appendChild(document.createTextNode(rm_count[i]));
    sdb_tbody.appendChild(row);
  }
}

function update_sequence_list(manager) {
  "use strict";
  var csc_span, textarea, format, seqs, mimetype, filename, desc, seqs_desc;
  csc_span = manager.block.querySelector(".sa_contr_seq_count");
  textarea = manager.block.querySelector(".sa_contr_seqs");
  format = manager.block.querySelector(".sa_contr_seqs_format").value;
  seqs = manager.spacing.seqs;
  seqs = seqs.map( function (seqidx) { return data.sequence_names[seqidx]; } );
  mimetype = "text/plain";
  desc =  "_g" + manager.spacing.bin + "_o" + manager.spacing.orient;
  seqs_desc = manager.primary.motif.id + "_with_" + manager.secondary.motif.id + desc;
  filename = "seqs_" + seqs_desc + ".txt";
  if (format == 1) {
    seqs = seqs.map( function (seqid) { return seqid.replace(/^([^:]+):(\d+)-(\d+)$/, "$1\t$2\t$3"); } );
    mimetype = "text/x-bed";
    filename = "seqs_" + seqs_desc + ".bed";
  }
  csc_span.textContent = seqs.length;
  textarea.value = seqs.join("\n");
  prepare_download(textarea.value, mimetype, filename,
      manager.block.querySelector(".sa_contr_seqs_dl"));
}

function download_all_contr_seqs(type) {
  var primary, filename;
  var secondaries, secondary, secondary_id;
  var spacings, spacing;
  var seqs, mimetype, text, pvalue;
  var i, j, k, n;

  text = "";
  n = 0;
  for (i = 0; i < data.primaries.length; i++) {
    primary = data.primaries[i].motif;
    secondaries = data.primaries[i].secondaries;
    for (j = 0; j < secondaries.length; j++) {
      secondary = secondaries[j];
      secondary_id = data.secondary_motifs[secondary.idx].id;
      spacings = secondary.spacings;
      for (k = 0; k < spacings.length; k++) {
        spacing = spacings[k];
        pvalue = spacing.pvalue;
        filename = "seqs_" + primary.id + "_with_" + secondary_id + 
          "_g" + spacing.bin + "_o" + spacing.orient;
        seqs = spacing.seqs;
        seqs = seqs.map( function (seqidx) { return data.sequence_names[seqidx]; } );
        if (type == "txt") {
          filename += '.txt';
          mimetype = "text/plain";
        } else if (type == "bed") {
          filename += '.bed';
          mimetype = "text/x-bed";
          seqs = seqs.map( function (seqid) { return seqid.replace(/^([^:]+):(\d+)-(\d+)$/, "$1\t$2\t$3"); } );
        } else {
          if (console && console.log) console.log("Unknown file type: ." + type);
        }
        if (n>0) text += "\n";
        n++;
        text += "# " + n + " " + filename + " " + pvalue + "\n" + seqs.join("\n");
      }
    }
  }
  //if (console && console.log) console.log(text);
  prepare_download(text, mimetype, "spamo_contr_seqs." + type);
}

//FIXME_TLB
function isArray(x) {
    return x.constructor.toString().indexOf("Array") > -1;
}

function update_selected_spacing(manager) {
  var slogo_out, desc, seqs_desc, lwidth;
  slogo_out = manager.block.querySelector(".sa_slogo_out");
  slogo_out.width = 0;
  desc =  "_gap_" + manager.spacing.bin + "_orientation_" + manager.spacing.orient;
  seqs_desc = manager.primary.motif.id + "_with_" + manager.secondary.motif.id + desc;
  var shift = manager.secondary.motif.ltrim;
  make_pwm_logo(slogo_out, manager.block.querySelector(".sa_slogo_out_meme"),
      manager.block.querySelector(".sa_slogo_out_eps"),
      manager.spacing.inferred_pwm, false,
      manager.secondary.motif.id + "_near_" + manager.primary.motif.id + desc, shift);
  lwidth = 0;
  lwidth = make_pwm_logo(manager.block.querySelector(".sa_align_up_pos"),
      manager.block.querySelector(".sa_align_up_pos_meme"),
      manager.block.querySelector(".sa_align_up_pos_eps"),
      manager.spacing.alignment_pwm[SpamoQuadGraph.SAME_LEFT], true,
      "alignseqs_up+_" + seqs_desc, 0);
  lwidth = Math.max(make_pwm_logo(manager.block.querySelector(".sa_align_up_neg"),
      manager.block.querySelector(".sa_align_up_neg_meme"),
      manager.block.querySelector(".sa_align_up_neg_eps"),
      manager.spacing.alignment_pwm[SpamoQuadGraph.OPPOSITE_LEFT], true,
      "alignseqs_up-_" + seqs_desc, 0), lwidth);
  lwidth = Math.max(make_pwm_logo(manager.block.querySelector(".sa_align_dn_pos"),
      manager.block.querySelector(".sa_align_dn_pos_meme"),
      manager.block.querySelector(".sa_align_dn_pos_eps"),
      manager.spacing.alignment_pwm[SpamoQuadGraph.SAME_RIGHT], true,
      "alignseqs_dn+_" + seqs_desc, 0), lwidth);
  lwidth = Math.max(make_pwm_logo(manager.block.querySelector(".sa_align_dn_neg"),
      manager.block.querySelector(".sa_align_dn_neg_meme"),
      manager.block.querySelector(".sa_align_dn_neg_eps"),
      manager.spacing.alignment_pwm[SpamoQuadGraph.OPPOSITE_RIGHT], true,
      "alignseqs_dn-_" + seqs_desc, 0), lwidth);
  manager.block.querySelector(".sa_align_scroll").style.width = lwidth + "px";
  var scroller = manager.block.querySelector(".sa_align_scrollbox");
  var scroll = Math.max(scroller.scrollWidth - scroller.offsetWidth, 0) / 2;
  scroller.scrollLeft = scroll;
  manager.block.querySelector(".sa_scroll_up_pos").scrollLeft = scroll;
  manager.block.querySelector(".sa_scroll_dn_pos").scrollLeft = scroll;
  manager.block.querySelector(".sa_scroll_up_neg").scrollLeft = scroll;
  manager.block.querySelector(".sa_scroll_dn_neg").scrollLeft = scroll;
  make_alignment_eps(manager);
  make_secondary_pair_eps(manager);
  update_sequence_list(manager);
  draw_overview_graph(manager);
  draw_sg(manager);
}

function make_spacing_handler(manager, spacing, row, table) {
  if (manager.spacing == spacing) {
    manager.spacing_row = row;
    toggle_class(row, "active", true);
    substitute_classes(table, ["orient_0", "orient_1", "orient_2", "orient_3",
        "orient_4", "orient_5", "orient_6", "orient_7", "orient_8"],
        ["orient_" + spacing.orient]);
    update_selected_spacing(manager);
  }
  return function(e) {
    // check if already selected
    if (manager.spacing === spacing) return; // ignore multiple clicks
    // update selection
    if (typeof manager.spacing_row !== "undefined") toggle_class(manager.spacing_row, "active", false);
    toggle_class(row, "active", true);
    substitute_classes(table, ["orient_0", "orient_1", "orient_2", "orient_3",
        "orient_4", "orient_5", "orient_6", "orient_7", "orient_8"],
        ["orient_" + spacing.orient]);
    manager.spacing = spacing;
    manager.spacing_row = row;
    update_selected_spacing(manager);
  };
}

function update_selected_secondary(manager) {
  function _set_text(node, text, defval) {
    if (typeof defval === "undefined") defval = "";
    node.innerHTML = "";
    node.appendChild(document.createTextNode((typeof text !== "undefined" ? text : defval)));
  }
  var name, table, tbody, tr, i;
  var spacings = manager.secondary.spacings;
  var best_sp = (spacings.length > 0 ? spacings[0] : null);
  // fill in details of the secondary
  name = manager.block.querySelector(".sa_name");
  name.innerHTML = "";
  name.appendChild(make_name(manager.secondary.motif, false, true));
  //_set_text(manager.block.querySelector(".sa_cluster"), manager.secondary.cluster.motif.id);
  _set_text(manager.block.querySelector(".sa_cluster"), make_name_txt(manager.secondary.cluster.motif));
  _set_text(manager.block.querySelector(".sa_evalue"), (best_sp.pvalue * manager.nmotifs).toExponential(2));
  _set_text(manager.block.querySelector(".sa_gap"), best_sp.bin);
  _set_text(manager.block.querySelector(".sa_orient"), orient_desc[best_sp.orient]);
  // fill in the secondary logo
  make_logo(manager.block.querySelector(".sa_slogo_in"), manager.secondary.motif);
  // get the spacings table
  table = manager.block.querySelector(".sa_spacings");
  // clear the table
  for (i = table.tBodies.length - 1; i >= 0; i--) {
    table.removeChild(table.tBodies[i]);
  }
  // now add all spacings
  tbody = document.createElement("tbody");
  for (i = 0; i < spacings.length; i++) {
    var spacing = spacings[i];
    tr = document.createElement("tr");
    tr.className = "sa_sp_row orient_" + spacing.orient;
    add_text_cell(tr, spacing.bin, "sa_sp_gap"); // spacing gap
    add_text_cell(tr, orient_names[spacing.orient], "sa_sp_orient"); // spacing orientation
    add_text_cell(tr, spacing.pvalue.toExponential(2), "sa_sp_pvalue"); // spacing p-value
    // check for clicks
    tr.addEventListener("click", make_spacing_handler(manager, spacing, tr, table), false);
    tbody.appendChild(tr);
  }
  table.appendChild(tbody);
  
}

function make_secondary_handler(manager, secondary, row) {
  if (manager.secondary === secondary) {
    manager.secondary_row = row;
    toggle_class(row, "active", true);
  }
  return function(e) {
    // check if already selected
    if (manager.secondary === secondary) return; // ignore multiple clicks
    // update selection
    if (typeof manager.secondary_row !== "undefined") toggle_class(manager.secondary_row, "active", false);
    if (typeof manager.spacing_row !== "undefined") toggle_class(manager.spacing_row, "active", false);
    toggle_class(row, "active", true);
    manager.secondary = secondary;
    manager.secondary_row = row;
    manager.spacing = secondary.spacings[0];
    delete manager.spacing_row;
    update_selected_secondary(manager);
  };
}

function make_hover_handler(secondary) {
  if (typeof make_hover_handler.timer === "undefined") make_hover_handler.timer = null;
  return function(e) {
    move_logo(e);
    this.addEventListener('mousemove', move_logo, false);
    if (make_hover_handler.motif_idx === secondary.idx) { 
      $("logo_popup").style.display = "block";
    } else {
      if (make_hover_handler.timer) clearTimeout(make_hover_handler.timer);
      make_hover_handler.timer = setTimeout(function() {
        var motif, pspm, logo, canvas;
        // create the motif pspm
        motif = secondary.motif;
        pspm = new Pspm(motif.pwm, motif.id, motif.ltrim, motif.rtrim, motif.nsites);
        // draw given motif
        canvas = $("logo_popup_canvas");
        logo = logo_1(alphabet, "", pspm);
        draw_logo_on_canvas(logo, canvas, false, 0.5);
        // draw RC motif
        canvas = $("logo_popup_canvas_rc");
        if (alphabet.has_complement()) {
          pspm.reverse_complement(alphabet);
          logo = logo_1(alphabet, "", pspm);
          draw_logo_on_canvas(logo, canvas, false, 0.5);
        } else {
          canvas.style.display = "none"; 
        }
        // record the displayed motif
        make_hover_handler.motif_idx = secondary.idx;
        make_hover_handler.timer = null;
        // show the popup
        $("logo_popup").style.display = "block";
      }, 200);
    }
  };
}

function dehover_handler() {
  var popup;
  if (make_hover_handler.timer) {
    clearTimeout(make_hover_handler.timer);
    make_hover_handler.timer = null;
  }
  popup = $("logo_popup");
  popup.style.display = "none";
  this.removeEventListener('mousemove', move_logo, false);
}

/*
 * move_logo
 * 
 * keeps the motif logo at a set distance from the cursor.
 */
function move_logo(e) {
  var popup = $("logo_popup");
  popup.style.left = (e.pageX + 20) + "px";
  popup.style.top = (e.pageY + 20) + "px";
}

function make_lock_handler(secondary) {
  return function(e) {
    secondary.locked = this.checked;
    e.stopPropagation();
  };
}

function create_secondary_row(manager, secondary) {
  // get the best spacing (if one exists)
  var best_sp = (secondary.spacings.length > 0 ? secondary.spacings[0] : null);
  // create the string describing the evalue
  var evalue_str = (best_sp.pvalue * manager.nmotifs).toExponential(2);
  // create the string describing the best gap
  var gap = (best_sp.bin * data.options.bin_size);
  var gap_end = gap + data.options.bin_size - 1;
  var gap_str = "" + gap + (gap_end > gap ? " - " + gap_end : "");
  // create the string describing the orientation
  var orient_str = orient_desc[best_sp.orient];
  // create the row
  var tr = document.createElement("tr");
  tr.className = "sa_row";
  var chk_lock = document.createElement("input");
  chk_lock.type = "checkbox";
  chk_lock.checked = secondary.locked;
  chk_lock.addEventListener("click", make_lock_handler(secondary), false);
  add_cell(tr, chk_lock, "sa_lock");
  add_text_cell(tr, secondary.motif.id, "sa_id"); // secondary motif id
  add_text_cell(tr, secondary.motif.alt, "sa_id"); // secondary motif alt
  add_text_cell(tr, secondary.cluster.motif.id, "sa_cluster"); // cluster
  add_text_cell(tr, evalue_str, "sa_evalue");// secondary best spacing E-value
  add_text_cell(tr, gap_str, "sa_gap");
  add_text_cell(tr, orient_str);
  add_cell(tr, make_compact_graph(secondary), "sa_spacings"); // spacing diagram // make_minimal_spacing_diagram(secondary.spacings, 0, 150)
  tr.addEventListener("click", make_secondary_handler(manager, secondary, tr), false);
  tr.addEventListener("mouseover", make_hover_handler(secondary), false);
  tr.addEventListener("mouseout", dehover_handler, false);
  return tr;
}

function update_secondaries_list(manager) {
  var table, tbody, tr, i, last;
  var secondaries;
  // now get the elements we're manipulating
  table = manager.block.querySelector(".sa_table");
  // clear the table
  for (i = table.tBodies.length - 1; i >= 0; i--) {
    table.removeChild(table.tBodies[i]);
  }
  // now make a filtered copy of the secondaries
  secondaries = manager.primary.secondaries.filter(function (elem) {
    if (elem === manager.secondary) return true;
    if (elem.locked) return true;
    if (manager.filter_id != null && !manager.filter_id.test(elem.motif.id)) return false;
    if (manager.filter_name != null && !manager.filter_name.test(typeof elem.motif.alt === "string" ? elem.motif.alt : "")) return false;
    if (manager.filter_cluster != null && !manager.filter_cluster.test(elem.cluster.motif.id)) return false;
    if (manager.filter_pvalue != null && elem.spacings[0].pvalue > manager.filter_pvalue) return false;
    if (manager.filter_bins != null) {
      var pv = (manager.filter_pvalue == null ? 1 : manager.filter_pvalue);
      var i, spacing;
      for (i = 0; i < elem.spacings.length; i++) {
        spacing = elem.spacings[i];
        // exit loop if any spacings pass filter
        if (manager.filter_bins[spacing.bin] && spacing.pvalue < pv) break;
      }
      if (i == elem.spacings.length) return false;
    }
    return true;
  });
  // sort
  secondaries.sort(manager.sort);
  // limit to top N secondaries keeping any locked items that would otherwise be excluded
  if (manager.filter_top != null && secondaries.length > manager.filter_top) {
    last = secondaries.length;
    for (i = secondaries.length - 1; i >= manager.filter_top; i--) {
      if (secondaries[i].locked || secondaries[i] === manager.secondary) {
        if ((i+1) < last) secondaries.splice(i+1, last - (i+1));
        last = i;
      }
    }
    if ((i+1) < last) secondaries.splice(i+1, last - (i+1));
  }
  tbody = document.createElement("tbody");
  for (i = 0; i < secondaries.length; i++) {
    tbody.appendChild(create_secondary_row(manager, secondaries[i]));
  }
  table.appendChild(tbody);
}

function make_contr_seqs_format_handler(manager) {
  return function(e) {
    update_sequence_list(manager);
  };
}

function make_alignment_scroll_handler(scroller, up_pos, dn_pos, up_neg, dn_neg) {
  return function(e) {
    var left = scroller.scrollLeft;
    up_pos.scrollLeft = left;
    dn_pos.scrollLeft = left;
    up_neg.scrollLeft = left;
    dn_neg.scrollLeft = left;
  };
}

function parse_filter_bins(field) {
  if (field.disabled) return null;
  var value = field.value;
  if (!/^[0-9,\- ]*$/.test(value)) return null;
  try {
    // set all the bins to off
    var bins = [];
    var i;
    for (i = 0; i < data.options.bin_pvalue_calc_range; i += data.options.bin_size) bins.push(false);
    // split the input into parts
    var re = /(\d+|-|,)/g;
    var m;
    var items = [];
    while (m = re.exec(value)) items.push(m[1]);
    // process the parts
    var last = -1; // track the last used item
    for (i = 0; i < items.length; i++) {
      var num1, num2;
      if (items[i] == "-") { // found a dash indicating a range
        // check that there is a number before that hasn't been used already
        if ((i - 1) <= last) throw new Error("Range start already used");
        if (!/^\d+$/.test(items[i - 1])) throw new Error("Range start not a number");
        num1 = parseInt(items[i - 1]);
        // check that there is a number after
        if ((i + 1) >= items.length) throw new Error("Range end not available");
        if (!/^\d+$/.test(items[i + 1])) throw new Error("Range end not a number");
        num2 = parseInt(items[i + 1]);
        // swap them if the first is larger than the second
        if (num1 > num2) {
          var temp = num1;
          num1 = num2;
          num2 = temp;
        }
        // update the last used item and skip over the second number we used
        last = ++i;
      } else if (items[i] == ",") { // found a comma which we can safely skip
        // update the last used item
        last = i;
        continue;
      } else { // by a process of elimination this must be a number
        // check to see if there is a next item and if it is a dash indicating a range
        if ((i + 1) < items.length && items[i + 1] == "-") continue; // range upcomming so don't process this one yet
        // parse the number as a range of size one
        num1 = parseInt(items[i]);
        num2 = num1;
        // update the last used item
        last = i;
      }
      // mark any bins touched by the gap range
      for (i = num1; i <= num2; i++) {
        bins[Math.floor(i / data.options.bin_size)] = true;
      }
    }
    // return the filtered bins
    return bins;
  } catch (err) {
    return null;
  }
}

function make_sort_handler(manager) {
  return function(e) {
    var sort_input, filter_top, filter_id, filter_name, filter_cluster,
        filter_evalue, value;
    var sci_num_re = /^\s*([+]?\d+(?:\.\d+)?)(?:[eE]([+-]\d+))?\s*$/;
    // set number of results to return (excluding locked)
    filter_top = manager.block.querySelector(".sa_filter_ipt_top");
    if (!filter_top.disabled && /^\s*\d+\s*$/.test(value = filter_top.value)) {
      manager.filter_top = parseInt(value);
    } else {
      manager.filter_top = null;
    }
    // set filter on ID
    filter_id = manager.block.querySelector(".sa_filter_ipt_id");
    if (!filter_id.disabled) {
      try { manager.filter_id = new RegExp(filter_id.value, "i"); }
      catch (err) { manager.filter_id = null; }
    } else {
      manager.filter_id = null;
    }
    // set filter on name
    filter_name = manager.block.querySelector(".sa_filter_ipt_name");
    if (!filter_name.disabled) {
      try { manager.filter_name = new RegExp(filter_name.value, "i"); }
      catch (err) { manager.filter_name = null; }
    } else {
      manager.filter_name = null;
    }
    // set filter on cluster
    filter_cluster = manager.block.querySelector(".sa_filter_ipt_cluster");
    if (!filter_cluster.disabled) {
      try { manager.filter_cluster = new RegExp(filter_cluster.value, "i"); }
      catch (err) { manager.filter_cluster = null; }
    } else {
      manager.filter_cluster = null;
    }
    // set filter on evalue
    filter_evalue = manager.block.querySelector(".sa_filter_ipt_ev");
    if (!filter_evalue.disabled && sci_num_re.test(value = filter_evalue.value)) {
      manager.filter_pvalue = parseFloat(value) / manager.nmotifs;
    } else {
      manager.filter_pvalue = null;
    }
    // set filter on bins
    manager.filter_bins = parse_filter_bins(manager.block.querySelector(".sa_filter_ipt_gap"));
    // set sort function
    var sort_fns = [cmp_secondaries_by_id, cmp_secondaries_by_alt,
             cmp_secondaries_by_cluster, make_cmp_secondaries_by_evalue, 
             cmp_secondaries_by_gap, cmp_secondaries_by_orientation,
             cmp_secondaries_by_spacing];
    sort_input = manager.block.querySelector(".sa_sort");
    manager.sort = sort_fns[parseInt(sort_input.value, 10)](manager.filter_bins);
    // update the displayed table of results
    update_secondaries_list(manager);
  };
}

function setup_opt_input(manager, id, chk_idclass, lbl_idclass, ipt_idclass) {
  var chk, lbl, ipt;
  chk = manager.block.querySelector("." + chk_idclass);
  lbl = manager.block.querySelector("." + lbl_idclass);
  ipt = manager.block.querySelector("." + ipt_idclass);
  ipt.id = id;
  lbl.htmlFor = ipt.id;
  // add listeners
  chk.addEventListener("click", function() {
    ipt.disabled = !chk.checked;
  }, false);
  lbl.addEventListener("click", function() {
    if (!chk.checked) chk.click();
  }, false);
  // handle form resets
  if (chk.form != null) {
    chk.form.addEventListener("reset", function() {
      window.setTimeout(function() {
        ipt.disabled = !chk.checked;
      }, 50);
    }, false);
  }
  // set to current state
  ipt.disabled = !chk.checked;
}

function setup_filter_sort(manager, primaryi) {
  var sort_input, sort_label, update_button;
  // setup sort selector
  sort_input = manager.block.querySelector(".sa_sort");
  sort_label = manager.block.querySelector(".sa_sort_lbl");
  sort_input.id = "sort_" + primaryi;
  sort_label.htmlFor = sort_input.id;
  // filters
  setup_opt_input(manager, "filter_top_" + primaryi, "sa_filter_chk_top", "sa_filter_lbl_top", "sa_filter_ipt_top");
  setup_opt_input(manager, "filter_id_" + primaryi, "sa_filter_chk_id", "sa_filter_lbl_id", "sa_filter_ipt_id");
  setup_opt_input(manager, "filter_name_" + primaryi, "sa_filter_chk_name", "sa_filter_lbl_name", "sa_filter_ipt_name");
  setup_opt_input(manager, "filter_cluster_" + primaryi, "sa_filter_chk_cluster", "sa_filter_lbl_cluster", "sa_filter_ipt_cluster");
  setup_opt_input(manager, "filter_ev_" + primaryi, "sa_filter_chk_ev", "sa_filter_lbl_ev", "sa_filter_ipt_ev");
  // both the pvalue and gap fields work on the same checkbox
  setup_opt_input(manager, "filter_gap_" + primaryi, "sa_filter_chk_pv_gap", "sa_filter_lbl_gap", "sa_filter_ipt_gap");
  // update button
  update_button = manager.block.querySelector(".sa_update_filter_sort");
  update_button.addEventListener("click", make_sort_handler(manager), false);
}

function setup_highlight(manager, primaryi) {
  var all_chk, sel_chk, all_lbl, sel_lbl;
  all_chk = manager.block.querySelector(".sa_hl_all_chk");
  all_chk.id = "hl_all_" + primaryi;
  all_lbl = manager.block.querySelector(".sa_hl_all_lbl");
  all_lbl.htmlFor = all_chk.id;
  all_chk.addEventListener("click", function(e) {
    manager.highlight_all = this.checked;
    draw_overview_graph(manager);
    draw_sg(manager);
  }, false);
  sel_chk = manager.block.querySelector(".sa_hl_sel_chk");
  sel_chk.id = "hl_sel_" + primaryi;
  sel_lbl = manager.block.querySelector(".sa_hl_sel_lbl");
  sel_lbl.htmlFor = sel_chk.id;
  sel_chk.addEventListener("click", function(e) {
    manager.highlight_selected = this.checked;
    draw_overview_graph(manager);
    draw_sg(manager);
  }, false);
}

function make_spacing_analysis() {
  "use strict";
  var template, container, block, checkbox, label;
  var primary, orient, i, nmotifs;
  var manager;
  // hide things when no reverse complement avaliable
  if (spamo_alphabet.has_complement()) {
    document.getElementsByTagName('body')[0].className += " revcomp";
  }
  // count total motifs so we can do E-value conversions
  nmotifs = 0;
  for (i = 0; i < data.secondary_dbs.length; i++) {
    nmotifs += (data.secondary_dbs[i].loaded - data.secondary_dbs[i].excluded);
  }
  // get the template and hide it
  template = document.getElementById("tmpl_primary");
  // get the container
  container = template.parentNode;
  // empty the container of everything but the primary template
  // note that the primary template is hidden by css based on its ID
  container.removeChild(template);
  container.innerHTML = "";
  container.appendChild(template);
  // loop over primary motifs
  for (i = 0; i < data.primaries.length; i++) {
    primary = data.primaries[i];
    // make a copy of the template
    block = template.cloneNode(true);
    // set the id
    block.id = "primary_" + i;
    // create an object to track the selected objects
    manager = {};
    manager.nmotifs = nmotifs;
    manager.block = block;
    manager.primary = primary;
    manager.secondary = primary.secondaries[0];
    manager.spacing = primary.secondaries[0].spacings[0];
    manager.sort = cmp_secondaries_by_evalue;
    manager.filter_top = null;
    manager.filter_id = null;
    manager.filter_name = null;
    manager.filter_cluster = null;
    manager.filter_evalue = null;
    manager.filter_bins = null;
    manager.highlight_selected = true;
    manager.highlight_all = true;
    // store the manager in the block for easy access
    block.data_manager = manager;
    // set the name
    block.querySelector(".sa_primary_name").appendChild(make_name(primary.motif));
    // make the logo
    make_logo(block.querySelector(".sa_plogo_in"), primary.motif);
    // setup options
    setup_filter_sort(manager, i);
    setup_highlight(manager, i);
    // add event listener to format selector
    block.querySelector(".sa_contr_seqs_format").addEventListener("click", make_contr_seqs_format_handler(manager), false);
    block.querySelector(".sa_align_scrollbox").addEventListener("scroll",
        make_alignment_scroll_handler(
          block.querySelector(".sa_align_scrollbox"),
          block.querySelector(".sa_scroll_up_pos"),
          block.querySelector(".sa_scroll_dn_pos"),
          block.querySelector(".sa_scroll_up_neg"),
          block.querySelector(".sa_scroll_dn_neg")
          ), false);
    container.appendChild(block);
    // populate the list of secondaries
    update_secondaries_list(manager);
    update_selected_secondary(manager);
  }
}

function make_program_summary() {
  "use strict";
  $("version").appendChild(document.createTextNode(data.version));
  $("release").appendChild(document.createTextNode(data.release));
  $("cmd").value = data.cmd.join(" ");
  $("runtime").appendChild(document.createTextNode(data.run_time.real));
}

/*
 * cmp_spacing_by_pvalue
 */
function cmp_spacing_by_pvalue(a, b) {
  if (a == null || b == null) {
    if (a == null && b == null) {
      return 0;
    } else if (a == null) {
      return 1;
    } else if (b == null) {
      return -1;
    }
  }
  if (a.pvalue < b.pvalue) {
    return -1;
  } else if (a.pvalue > b.pvalue) {
    return 1;
  }
  if (a.bin < b.bin) {
    return -1;
  } else if (a.bin > b.bin) {
    return 1;
  }
  if (a.orient < b.orient) {
    return -1
  } else if (a.orient > b.orient) {
    return 1
  }
  return 0;
}

/*
 * cmp_secondaries_by_evalue
 */
function cmp_secondaries_by_evalue(a, b) {
  var cmp;
  if (a === b) return 0;
  if ((cmp = cmp_spacing_by_pvalue(a.spacings[0], b.spacings[0])) != 0) return cmp;
  if (a.idx < b.idx) {
    return -1;
  } else if (a.idx > b.idx) {
    return 1;
  }
  return 0;
}

/*
 * cmp_secondaries_by_id
 */
function cmp_secondaries_by_id(allowed_bins) {
  var ev_cmp = make_cmp_secondaries_by_evalue(allowed_bins);
  return function(a, b) {
    var a_alt, b_alt;
    if (a.motif.id < b.motif.id) {
      return -1;
    } else if (a.motif.id > b.motif.id) {
      return 1;
    }
    a_alt = (typeof a.motif.alt === "string" ? a.motif.alt : "");
    b_alt = (typeof b.motif.alt === "string" ? b.motif.alt : "");
    if (a_alt < b_alt) {
      return -1;
    } else if (a_alt > b_alt) {
      return 1;
    }
    return ev_cmp(a, b);
  };
}

/*
 * cmp_secondaries_by_alt
 */
function cmp_secondaries_by_alt(allowed_bins) {
  var ev_cmp = make_cmp_secondaries_by_evalue(allowed_bins);
  return function(a, b) {
    var a_alt, b_alt;
    a_alt = (typeof a.motif.alt === "string" ? a.motif.alt : "");
    b_alt = (typeof b.motif.alt === "string" ? b.motif.alt : "");
    if (a_alt < b_alt) {
      return -1;
    } else if (a_alt > b_alt) {
      return 1;
    }
    if (a.motif.id < b.motif.id) {
      return -1;
    } else if (a.motif.id > b.motif.id) {
      return 1;
    }
    return ev_cmp(a, b);
  };
}

/*
 * cmp_secondaries_by_cluster
 */
function cmp_secondaries_by_cluster(allowed_bins) {
  var ev_cmp = make_cmp_secondaries_by_evalue(allowed_bins);
  return function(a, b) {
    var cluster_cmp;
    cluster_cmp = ev_cmp(a.cluster, b.cluster);
    if (cluster_cmp != 0) return cluster_cmp;
    return ev_cmp(a, b);
  };
}

/*
 * cmp_secondaries_by_gap
 */
function cmp_secondaries_by_gap(allowed_bins) {
  var ev_cmp = make_cmp_secondaries_by_evalue(allowed_bins);
  return function(a, b) {
    var a_space, b_space;
    if (a === b) return 0;
    a_space = a.spacings[0];
    b_space = b.spacings[0];
    if (a_space.bin < b_space.bin) {
      return -1;
    } else if (a_space.bin > b_space.bin) {
      return 1;
    }
    return ev_cmp(a, b);
  };
}

/*
 * cmp_secondaries_by_orientation
 */
function cmp_secondaries_by_orientation(allowed_bins) {
  var ev_cmp = make_cmp_secondaries_by_evalue(allowed_bins);
  return function(a, b) {
    var a_space, b_space;
    if (a === b) return 0;
    a_space = a.spacings[0];
    b_space = b.spacings[0];
    if (a_space.orient < b_space.orient) {
      return -1
    } else if (a_space.orient > b_space.orient) {
      return 1
    }
    return ev_cmp(a, b);
  };
}

/*
 * cmp_secondaries_by_spacing
 */
function cmp_secondaries_by_spacing(allowed_bins) {
  return function(a, b) {
    var a_space, b_space;
    if (a === b) return 0;
    a_space = a.spacings[0];
    b_space = b.spacings[0];
    if (a_space.bin < b_space.bin) {
      return -1;
    } else if (a_space.bin > b_space.bin) {
      return 1;
    }
    if (a_space.orient < b_space.orient) {
      return -1
    } else if (a_space.orient > b_space.orient) {
      return 1
    }
    if (a_space.pvalue < b_space.pvalue) {
      return -1;
    } else if (a_space.pvalue > b_space.pvalue) {
      return 1;
    }
    if (a.idx < b.idx) {
      return -1;
    } else if (a.idx > b.idx) {
      return 1;
    }
    return 0;
  };
}

function make_cmp_secondaries_by_evalue(allowed_bins) {
  function _best_allowed_spacing(secondary) {
    var i;
    if (allowed_bins == null) return secondary.spacings[0];
    for (i = 0; i < secondary.spacings.length; i++) {
      if (allowed_bins[secondary.spacings[i].bin]) return secondary.spacings[i];
    }
    return null;
  }
  return function(a, b) {
    var a_pv, b_pv;
    var cmp;
    if (a === b) return 0;
    if ((cmp = cmp_spacing_by_pvalue(_best_allowed_spacing(a), _best_allowed_spacing(b))) != 0) return cmp;
    if (a.idx < b.idx) {
      return -1;
    } else if (a.idx > b.idx) {
      return 1;
    }
    return 0;
  };
}

function show_motif_pair(pri_db, pri_id, sec_db, sec_id, sp_orient, sp_bin) {
  var i, primaryi, primary, secondary, spacing, block, manager;
  // first determine which primary
  for (i = 0; i < data.primaries.length; i++) {
    primary = data.primaries[i];
    if (primary.motif.db == pri_db && primary.motif.id == pri_id) {
      primaryi = i;
      break;
    }
  }
  if (i == data.primaries.length) return; // no such primary
  // determine secondary
  if (typeof sec_db == "number" && typeof sec_id == "string") {
    for (i = 0; i < primary.secondaries.length; i++) {
      secondary = primary.secondaries[i];
      if (secondary.motif.db == sec_db && secondary.motif.id == sec_id) break;
    }
    if (i == primary.secondaries.length) return; // no such secondary
  } else {
    secondary = primary.secondaries[0];
  }
  // determine spacing
  if (typeof sp_orient == "number" && typeof sp_gap == "number") {
    for (i = 0; i < secondary.spacings.length; i++) {
      spacing = secondary.spacings[i];
      if (spacing.orient == sp_orient && spacing.bin == sp_bin) break;
    }
    if (i == secondary.spacings.length) return; // no such spacing
  } else {
    spacing = secondary.spacings[0];
  }
  // get the block
  block = document.getElementById("primary_" + primaryi);
  if (!block) return;
  // get the manager
  manager = block.data_manager;
  manager.primary = primary;
  manager.secondary = secondary;
  manager.spacing = spacing;
  update_secondaries_list(manager);
}

/*
 * Process data
 *
 * Preprocess the data
 */
(function() {
  var primaryi, secondaryi, secondaryj, i;
  var primary, secondary, secondaries;
  for (primaryi = 0; primaryi < data.primaries.length; primaryi++) {
    primary = data.primaries[primaryi];
    secondaries = [];
    for (secondaryi = 0; secondaryi < primary.secondaries.length; secondaryi++) {
      data.secondary_motifs[primary.secondaries[secondaryi][0].idx].nonredundant = true;
      for (secondaryj = 0; secondaryj < primary.secondaries[secondaryi].length; secondaryj++) {
        secondary = primary.secondaries[secondaryi][secondaryj];
        secondary.motif = data.secondary_motifs[secondary.idx];
        secondary.cluster = primary.secondaries[secondaryi][0];
        secondaries.push(secondary);
      }
    }
    secondaries.sort(cmp_secondaries_by_evalue);
    primary.secondaries = secondaries;
  }
  for (i = 0; i < data.secondary_motifs.length; i++) {
    data.secondary_motifs[i].locked = false;
  }
})();

    </script>
    <style type="text/css">
/* The following is the content of meme.css */
body { background-color:white; font-size: 12px; font-family: Verdana, Arial, Helvetica, sans-serif;}

div.help {
  display: inline-block;
  margin: 0px;
  padding: 0px;
  width: 12px;
  height: 13px;
  cursor: pointer;
  background-image: url(data:image/gif;base64,R0lGODlhDAANAIABANR0AP///yH5BAEAAAEALAAAAAAMAA0AAAIdhI8Xy22MIFgv1DttrrJ7mlGNNo4c+aFg6SQuUAAAOw==);
}

div.help:hover {
  background-image: url(data:image/gif;base64,R0lGODlhDAANAKEAANR0AP///9R0ANR0ACH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEAAAIALAAAAAAMAA0AAAIdDGynCe3PgoxONntvwqz2/z2K2ImjR0KhmSIZUgAAOw==);
}

p.spaced { line-height: 1.8em;}

span.citation { font-family: "Book Antiqua", "Palatino Linotype", serif; color: #004a4d;}

p.pad { padding-left: 30px; padding-top: 5px; padding-bottom: 10px;}

td.jump { font-size: 13px; color: #ffffff; background-color: #00666a;
  font-family: Georgia, "Times New Roman", Times, serif;}

a.jump { margin: 15px 0 0; font-style: normal; font-variant: small-caps;
  font-weight: bolder; font-family: Georgia, "Times New Roman", Times, serif;}

h2.mainh {font-size: 1.5em; font-style: normal; margin: 15px 0 0;
  font-variant: small-caps; font-family: Georgia, "Times New Roman", Times, serif;}

h2.line {border-bottom: 1px solid #CCCCCC; font-size: 1.5em; font-style: normal;
  margin: 15px 0 0; padding-bottom: 3px; font-variant: small-caps;
  font-family: Georgia, "Times New Roman", Times, serif;}

h4 {border-bottom: 1px solid #CCCCCC; font-size: 1.2em; font-style: normal;
  margin: 10px 0 0; padding-bottom: 3px; font-family: Georgia, "Times New Roman", Times, serif;}

h5 {margin: 0px}

a.help { font-size: 9px; font-style: normal; text-transform: uppercase;
  font-family: Georgia, "Times New Roman", Times, serif;}

div.pad { padding-left: 30px; padding-top: 5px; padding-bottom: 10px;}

div.pad1 { margin: 10px 5px;}

div.pad2 { margin: 25px 5px 5px;}
h2.pad2 { padding: 25px 5px 5px;}

div.pad3 { padding: 5px 0px 10px 30px;}

div.box { border: 2px solid #CCCCCC; padding:10px; overflow: hidden;}

div.bar { border-left: 7px solid #00666a; padding:5px; margin-top:25px; }

div.subsection {margin:25px 0px;}

img {border:0px none;}

th.majorth {text-align:left;}
th.minorth {font-weight:normal; text-align:left; width:8em; padding: 3px 0px;}
th.actionth {font-weight:normal; text-align:left;}

.explain h5 {font-size:1em; margin-left: 1em;}

div.doc {margin-left: 2em; margin-bottom: 3em;}

th.trainingset {
  border-bottom: thin dashed black; 
  font-weight:normal; 
  padding:0px 10px;
}
div.pop_content {
  position:absolute;
  z-index:50;
  width:300px;
  padding: 5px;
  background: #E4ECEC;
  font-size: 12px;
  font-family: Arial;
  border-style: double;
  border-width: 3px;
  border-color: #AA2244;
  display:none;
}

div.pop_content > *:first-child {
  margin-top: 0px;
}

div.pop_content h1, div.pop_content h2, div.pop_content h3, div.pop_content h4, 
div.pop_content h5, div.pop_content h6, div.pop_content p {
  margin: 0px;
}

div.pop_content p + h1, div.pop_content p + h2, div.pop_content p + h3, 
div.pop_content p + h4, div.pop_content p + h5, div.pop_content p + h6 {
  margin-top: 5px;
}

div.pop_content p + p {
  margin-top: 5px;
}

div.pop_content > *:last-child {
  margin-bottom: 0px;
}

div.pop_content div.pop_close {
  /* old definition */
  float:right;
  bottom: 0;
}

div.pop_content span.pop_close, div.pop_content span.pop_back {
  display: inline-block;
  border: 2px outset #661429;
  background-color: #CCC;
  padding-left: 1px;
  padding-right: 1px;
  padding-top: 0px;
  padding-bottom: 0px;
  cursor: pointer;
  color: #AA2244; /*#661429;*/
  font-weight: bold;
}

div.pop_content span.pop_close:active, div.pop_content span.pop_back:active {
  border-style: inset;
}

div.pop_content span.pop_close {
  float:right;
  /*border: 2px outset #AA002B;*/
  /*color: #AA2244;*/
}

div.pop_content:not(.nested) .nested_only {
  display: none;
}

div.pop_back_sec {
  margin-bottom: 5px;
}

div.pop_close_sec {
  margin-top: 5px;
}

table.hide_advanced tr.advanced {
  display: none;
}
span.show_more {
  display: none;
}
table.hide_advanced span.show_more {
  display: inline;
}
table.hide_advanced span.show_less {
  display: none;
}


/*****************************************************************************
 * Program logo styling
 ****************************************************************************/
div.prog_logo {
  border-bottom: 0.25em solid #0f5f60;
  height: 4.5em;
  width: 24em;
  display:inline-block;
}
div.prog_logo img {
  float:left;
  width: 4em;
  border-style: none;
  margin-right: 0.2em;
}
div.prog_logo h1, div.prog_logo h1:hover, div.prog_logo h1:active, div.prog_logo h1:visited {
  margin:0;
  padding:0;
  font-family: Arial, Helvetica,  sans-serif;
  font-size: 3.2em;
  line-height: 1em;
  vertical-align: top;
  display: block;
  color: #026666;
  letter-spacing: -0.06em;
  text-shadow: 0.04em 0.06em 0.05em #666;
}
div.prog_logo h2, div.prog_logo h2:hover, div.prog_logo h2:active, div.prog_logo h2:visited {
  display: block;
  margin:0;
  padding:0;
  font-family: Helvetica, sans-serif;
  font-size: 0.9em;
  line-height: 1em;
  letter-spacing: -0.06em;
  color: black;
}

div.big.prog_logo {
  font-size: 18px;
}


    </style>
    <style type="text/css">
table.inputs, table.alpha_bg_table {
  margin-top: 20px;
  border-collapse:collapse;
}

table.inputs * td, table.inputs * th, table.alpha_bg_table * td, table.alpha_bg_table * th {
  padding-left: 15px;
  padding-right: 15px;
  padding-top: 1px;
  padding-bottom: 1px;
}

div.header {
  position: relative;
  overflow: hidden;
  margin-top: 15px;
  margin-bottom: 5px;
  margin-right: 3px;
  margin-left: 3px;
}
div.header > h2 {
  font-size: 1.5em;
  font-style: normal;
  margin: 0;
  font-variant: small-caps; 
  font-family: Georgia, "Times New Roman", Times, serif;
}
div.header > span {
  position: absolute;
  right: 0;
  bottom: 0;
}

table {
  border-collapse: collapse;
}

table.nowrap th, table.nowrap td {
  white-space: nowrap;
}

table.th_ul th {
  white-space: nowrap;
  font-size: 1.2em;
  font-style: normal;
  font-family: Georgia, "Times New Roman", Times, serif;
}

table.th_ul th > span.th_ul {
  display: block;
  border-bottom: 1px solid #CCCCCC;
  margin-right: 3px;
  padding-bottom: 1px; 
}
table.th_ul td, table.th_ul th {
  padding-right: 10px;
}
div.ml {
  line-height: 1.8em;
  height: 3.6em;
  overflow-y: auto;
}

body:not(.revcomp) .revcomp {
  visibility: hidden;
}


td.pri_nlist {
  text-align: center;
}

td.seq_loaded, td.seq_too_short, td.seq_ambiguous, td.seq_no_primary, td.seq_too_similar, td.seq_used {
  text-align: center;
}

textarea.sa_contr_seqs {
  height: 300px;
}

div.sa_spacings_anchor {
  position: relative;
}

div.sa_spacings_scroll {
  height: 300px;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right:10px;
}

table.orient_0 tr.orient_0,
table.orient_1 tr.orient_1,
table.orient_2 tr.orient_2,
table.orient_3 tr.orient_3,
table.orient_4 tr.orient_4,
table.orient_5 tr.orient_5,
table.orient_6 tr.orient_6,
table.orient_7 tr.orient_7,
table.orient_8 tr.orient_8  {
  background: #EEE;
}

table.sa_spacings tr.sa_sp_row:hover, tr.sa_row:hover {
  background: #E4ECEC;
  cursor: pointer;
}

table.sa_spacings tr.sa_sp_row.active, tr.sa_row.active {
  background: #026666;
  color: white;
}

tr.fixed_header th {
  color: transparent;
  white-space: nowrap;
}
tr.fixed_header th div {
  color: black;
  background: #FFF;
  position: absolute;
  top: 0;
}

tr.fixed_header div.fill_back {
  left: 0; 
  right: 15px;
  height: 1em;
}

input.sa_sp_select {
}

td.sa_sp_gap {
  text-align: right;
}

td.sa_sp_orient {
  text-align: center;
}

td.sa_sp_pvalue {
  text-align: left;
}

div.sp_info {
  display: inline-block;
  vertical-align:top;
}

div.sp_info h3 {
  margin: 0;
  padding: 0;
  white-space: nowrap;
  font-size: 1.2em;
  font-style: normal;
  font-family: Georgia, "Times New Roman", Times, serif;
  border-bottom: 1px solid #CCCCCC;
  margin-right: 3px;
  padding-bottom: 1px; 
  text-align: center;
}

#tmpl_primary {
  display: none;
}

canvas.sa_align_up_pos, canvas.sa_align_up_neg, canvas.sa_align_dn_pos, canvas.sa_align_dn_neg {
  display: block;
}

div.align_scroll {
  width: 99%;
  overflow-x: auto;
}

a.sa_contr_seqs_dl {
  padding-left: 10px;
}

div.pop_logo {
  position:absolute;
  z-index:1;
  top:0px;
  left:0px;
  border: 3px outset black;
  background-color: white;
  display:none;
}

table.sa_summary {
  width: 100%;
  font-size: 1.2em;
  font-style: normal;
  font-family: Georgia, "Times New Roman", Times, serif;
  margin-bottom: 20px;
}

table.sa_summary tr th {
  text-align: right;
}

table.sa_summary tr td {
  text-align: left;
  padding-left: 5px;
}

.overlink {
  display: inline-block;
  position: relative;
}

.overlink a {
  display: inline-block;
  position: relative;
  top: 50%;
  transform: translateY(-50%);
  padding: 5px;
  margin-left: auto;
  margin-right: auto;
  background-color: rgba(255, 255, 255, 0.8);
}
.overlink span {
  display: block;
  position: absolute;
  width: 100%;
  height: 50%;
  text-align: center;
  display: none;
}

.overlink:hover span {
  display: block;
}

.overlink span.top {
  top: 0;
}
.overlink span.base {
  bottom: 0;
}

.pop_content dl dt {
  font-weight: bold;
}

.sa_align_scrollbox {
  width: 500px;
  overflow-x: auto;
}

.sa_align_scroll {
  height: 1px;
  width: 1px;
  display: inline-block;
}

.sa_scroll_up_pos, .sa_scroll_up_neg, .sa_scroll_dn_pos, .sa_scroll_dn_neg {
 width: 500px;
 overflow-x: hidden;
}

span.sec_best, span.sec_all {
  display: none;
}
td.best span.sec_best, td.all span.sec_all {
  display: inline;
}

span.xalph_true, span.xalph_false {
  display: none;
}
td.convert span.xalph_true, td.no_convert span.xalph_false {
  display: inline;
}

    </style>
  </head>
  <body data-scrollpad="true">
    <!-- {{{ help topics -->
    <div class="pop_content" id="pop_alph_name">
      <script>print_doc_para('pop_alph_name', 'alph-name', 'The ');</script>
    </div>
    <div class="pop_content" id="pop_alph_bg">
      <script>print_doc_para('pop_alph_bg', 'alph-bg', 'The ');</script>
    </div>
    <div class="pop_content" id="pop_results">
      <script>print_doc("pop_results", "spamo-results-tsv");</script>
      <script>print_doc("pop_results", "motif-consensus");</script>
      <div style="float:right; bottom:0px;">[
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_primary_db">
      <p>The database of the primary motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_primary_name">
      <p>The ID of the primary motif followed by the alternate ID in brackets if it has one.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_primary_preview">
      <p>The logo of the primary motif.</p>
      <p>Sections of the motif with a gray background have been trimmed and 
        were not used for scanning.</p>
      <div style="float:right; bottom:0px;">[
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_primary_nsig">
      <p>The number of secondary motifs found that had significant spacings in 
        the tested region.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_primary_sigs">
      <p>The list of secondary motifs found that had significant spacings in 
        the tested region.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_name">
      <p>The name of the sequence database.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_last_modified">
      <p>The last modified date of the sequence database.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_loaded">
      <p>The number of sequences in the sequence database.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_too_short">
      <p>The number of sequences in the sequence database which were excluded 
        because they were shorter than twice the margin plus the primary motif 
        length.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_ambiguous">
      <p>The number of sequences in the sequence database which were excluded 
        because they contained large runs of ambiguous symbols (normally
        wildcard masking) that could bias the results.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_no_primary">
      <p>The number of sequences in the sequence database which were excluded 
        because no match to the primary motif could be found at a distance to 
        the edges larger than the margin.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_too_similar">
      <p>The number of sequences in the sequence database which were excluded 
        because they were largely identical to other sequences when aligned on
        the primary motif site.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seqs_used">
      <p>The number of sequences which were scanned with the secondary 
        motifs.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sdb_name">
      <p>The name of the motif database derived from the file name.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sdb_last_modified">
      <p>The date that the motif database was last modified.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sdb_num_motifs">
      <p>The number of motifs loaded from the motif database. Some motifs may 
        have been excluded.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sdb_sig_motifs">
      <p>The number of motifs with significant <i>E</i>-values whose
        significant spacings were not considered too similar to those of another motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sdb_redundant_motifs">
      <p>The number of motifs that while having significant spacings were less 
        significant than another motif that matched
      most of the same sites.</p>
    <div style="float:right; bottom:0px;">[ 
      <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_sa_lock">
      <p>This checkbox ensures the row stays visible after a filter operation
      that would normally hide it.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_id">
      <p>The ID of the secondary motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_alt">
      <p>The alternate name of the secondary motif.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_name">
      <p>The ID of the secondary motif followed by the alternate ID in brackets if it has one.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_cluster">
      <p>The name of the cluster to which this secondary motif belongs.
        SpaMo assigns each secondary motif to a cluster, and names the
	cluster after the motif in it with the most significant spacing.
	SpaMo assigns two secondary motifs to the same cluster if the matches
	in their most significant spacings (from the primary motif) overlap substantially.
	Clustering is controlled by the -joint and -overlap options.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_evalue">
      <p>The <i>E</i>-value is the lowest <i>p</i>-value of any spacing of the
        secondary motif times the number of secondary motifs.
        It estimates the expected number of <i>random</i> secondary motifs that
        would have the observed minimum <i>p</i>-value or less.
      </p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_gap">
      <p>The gap between the primary and secondary motifs for the most significant spacing.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_orient">
      <p>The strand and position of the secondary motif relative to the primary motif for the most significant spacing.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_minscore">
      <p>The minimum score accepted as a match to either the primary or secondary motif.  This value
        can greatly affect the results of SpaMo.  If it is too high, there will be no
        matches to the primary motif.  If too low, sequences with non-significant matches
        to the primary and/or secondary motif will reduce the effectiveness of the spacing analysis.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_margin">
      <p> The distance either side of the primary motif site which makes up the region that 
        can contain the secondary motif site. Additionally it is the minimum gap between 
        the primary motif site and the edge of the sequence. These constraints mean that 
        input sequences shorter than the trimmed length of the primary motif plus two 
        times the margin size can not be used by SpaMo.
      </p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sa_spacings">
      <p>A histogram showing the counts for the orientation with the best spacing.</p>
      <p>The significant spacings are highlighted in red.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_primary">
      <p>The primary motif is used as the reference point for all spacing 
        calculation.</p>
      <p>Sections of the motif with a gray background have been trimmed and 
        were not used for scanning.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_secondary">
      <p>The secondary motif occurs at the spacings relative to the primary 
        shown in the histogram below.</p>
      <p>Sections of the motif with a gray background have been trimmed and 
        were not used for scanning.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_inferred_secondary">
      <p>The regions matching the secondary motif in the sequences with
        the given spacing are used to construct a motif.  The logo
        for this "inferred" motif is shown aligned with that of the actual
        secondary motif.</p>
      <p>The inferred secondary motif logo should closely resemble that
        of the secondary motif.  If it does not, this may suggest that
        the observed spacing may actually be due to the enrichment of a motif
        that differs from the secondary motif.</p>
      <p>You can download the inferred secondary motif by moving the mouse
        cursor over the logo and clicking "Download as MEME motif". You can then
        use this downloaded motif as an input to Tomtom to see what other known
        motifs it may resemble.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_alignment_logos">
      <p>These are the sequence logos created by aligning all of the
        sequences with the significant motif spacing. Alignments
        are centered on the match to the primary motif and done
        separately for each of the quadrants that contribute to the
        significant spacing.  The logos extend in both
        directions (up to) 10 positions past the maximum region 
        considered in the significance tests.

     <p><b>Note 1:</b> If you don't
	see the complete logo(s), you can use the scroll bar underneath
	the Alignment window.  If you don't see a scroll bar and are
	on a Mac, you can turn on scroll bars by clicking on the Apple Icon
	at the top left of your terminal and clicking:
	<code>System Preferences/General/Show scroll bars/Always</code>.</p>

      <p><b>Note 2:</b>These logos are useful for detecting cases where highly
        similar regions (such as DNA repeats) are present 
        among the sequences with the significant motif spacing.
        Such cases may indicate that the spacing is due
        to recent duplication events rather than to
        a functional biological relationship between
        the primary and secondary motifs.  Ideally, the 
        regions around the primary and secondary motifs should
        have low information content and their logos in the
        alignment should closely match their motifs.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>

    <div class="pop_content" id="pop_spacings">
      <p>This table shows the details of the significant spacings between the primary motif
      and the secondary motif currently selected in the "Secondaries" section, below.
      Click on a row in this table to select a particular spacing for detailed analysis.</p>
      <dl>
      <dt>Gap</dt> 
      <dd>is the space between the primary and secondary motifs where a value 
        of zero means there is no space between them. Note that if a motif
        has had low information content areas trimmed off this is the
        gap to the first untrimmed position.</dd> 
      <dt>Orientation</dt> 
      <dd>is the combination of quadrants used. Possible values are:
      individual quadrants (up+, up-, dn+, dn-) which are important
      when neither motif is palindromic; the diagonally combined quadrants
      (up+/dn-, up-/dn+) which are important when only the primary motif is
      palindromic; the vertically combined quadrants (up+/up-, dn+/dn-) which
      are important when only the secondary motif is palindromic; and all
      quadrants combined together (all) which is important when both motifs are
      palindromes.</dd> 
      <dt><i>P</i>-value</dt> 
      <dd>is the probability of the observed number (or more) sequences
          having the observed spacing between the primary and secondary motif,
          adjusted for multiple tests.  The number of multiple tests is the 
          number of spacing bins (the number of bars in one quadrant of the histogram)
          times the number of combinations of quadrants (nine)
          tested for significance.</dd> 
      </dl>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_histogram">
      <p>The histogram below shows the frequency of spacings from the primary 
        motif to the secondary motif.</p>
      <p>The two quadrants on the left show spacings where the secondary motif
      is upstream of the primary motif and the two quadrants on the right show
      spacings where the secondary motif is downstream of the primary motif.</p>
      <p>The two quadrants on the top show spacings where the secondary motif
      is on the same strand as the primary motif and the two quadrants on the
      bottom show spacings where the secondary motif is on the opposite strand
      to the primary motif.</p>
      <p>Histogram bars highlighted pink are part of one of the listed significant
      spacings. This feature can be disabled by unchecking the "highlight all"
      option under the spacings.</p>
      <p>Histogram bars highlighted red are part of the currently selected
      significant spacing. This feature can be disabled by unchecking the
      "highlight selected" option under the spacings.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_selected_orient">
      <p>The selected orientation graph shows the combined quadrants from the
      selected spacing with a zoomed view that only shows the portion of the
      graph for which significance testing was performed.</p>
      <p>Histogram bars highlighted pink are one of the listed significant
      spacings for this orientation. This feature can be disabled by unchecking
      the "highlight all" option under the spacings.</p>
      <p>The histogram bar highlighted red is the currently selected
      significant spacing. This feature can be disabled by unchecking the
      "highlight selected" option under the spacings.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_download_seqs">
      <p>This causes a file named <code>spamo_contr_seqs.txt</code> or 
	<code>spamo_contr_seqs.bed</code>
	to be downloaded.  The file contains the <b>contributing sequence IDs</b> 
	for each significant spacing.</p>
      <p>Each group of sequence IDs begins
	with a comment line containing (1) the rank of the spacing, (2) the name of the file
	that would contain the sequence IDs if you had used the 
	"Contributing Sequence IDs Download" function for a single spacing, 
	and (3) the <i>p</i>-value of the spacing.  (<b>Note: </b>See the help bubble for
	"Contributing Sequence IDs", below, for the format and meaning of the file names.)</p>
      <p>The sequence identifiers will be as they appear in the input sequence file
	(Plain) or in UCSC Genome Browser format (BED), 
	depending on which file you choose to download.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_seq_ids">
      <p>This lists the sequence identifiers of the subset of sequences
        that contain the significant motif spacing.  You can choose either
	the original sequence ID format (Plain) or UCSC Genome Browser format 
	(BED) using the menu below.
      </p>
      <p>These identifiers can be cut-and-pasted into other 
        programs for further analysis (e.g., Genome Ontology
        analysis or location analysis in the case ChIP-seq peak regions).</p>
      <p>
	You can also download the identifiers using the "Download" link below.
	They will be placed in a file with name:
        <table>
	  <tr><th><code>seqs_&lt;prim&gt;_with_&lt;scnd&gt;_g&lt;gap&gt;_o&lt;orient&gt;</code></th></tr>
        </table>
	and extension <code>.txt</code> if you choose "Plain Format"
	or with the extension <code>.bed</code> if you choose BED format. 
	The fields in brackets in the file name have the following meanings:
        <table>
        <table class="dark" style="width:100%" border=1>
          <tr> <th>name</th> <th>meaning</th> </tr>
          <tr><td><code>&lt;prim&gt;</code></td><td>the ID of the primary motif</td></tr>
          <tr><td><code>&lt;scnd&gt;</code></td><td>the ID of the secondary motif</td></tr>
          <tr><td><code>&lt;gap&gt;</code></td><td>the width of the spacing</td></tr>
          <tr><td><code>&lt;orient&gt;</td><td></code>an integer code denoting the 
 	  orientation of the spacing.</td></tr>
        </table>
      </p>
      <p>
        The orientation codes are:
        <table class="dark" style="width:100%" border=1>
          <tr> <th>orientation code</th> <th>enriched quadrant(s)</th> </tr>
          <tr> <td>0</td> <td>up+</td> </tr>
          <tr> <td>1</td> <td>dn+</td> </tr>
          <tr> <td>2</td> <td>up-</td> </tr>
          <tr> <td>3</td> <td>dn-</td> </tr>
          <tr> <td>4</td> <td>up+/up-</td> </tr>
          <tr> <td>5</td> <td>up+/dn-</td> </tr>
          <tr> <td>6</td> <td>up-/dn+</td> </tr>
          <tr> <td>7</td> <td>dn+/dn-</td> </tr>
          <tr> <td>8</td> <td>all</td> </tr>
        </table>
      </p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_secondaries">
      <p>Click on a row in this table to select one of the significant
      secondary motifs for detailed analysis.
      The details of the significant spacings between the primary motif
      and the secondary motif you select here will be displayed in the
      table and plots above.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_filter">
      <p>Specify which secondary motifs to display in the Secondaries table
	by checking one or more of the tick boxes below and then entering
        filter criteria.  Then click "Update" to refresh the view of the
        Secondaries table.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <div class="pop_content" id="pop_sort">
      <p>Specify the order in which secondary motifs are displayed in the Secondaries table
	by selecting a sorting criteria in the menu below.
        Then click "Update" to refresh the view of the
        Secondaries table.</p>
      <div style="float:right; bottom:0px;">[ 
        <a href="javascript:help_popup()">close</a> ]</div>
    </div>
    <!-- }}} -->

    <!-- logo popup -->
    <div id="logo_popup" class="pop_logo">
      <canvas id="logo_popup_canvas" width="200" height="50"></canvas>
      <canvas id="logo_popup_canvas_rc" width="200" height="50"></canvas>
    </div>

    <!-- page start -->
    <div class="pad1">
      <div class="prog_logo big">
        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB90JEQIQENMwIYEAABxZSURBVHja7Zx5sJ13ed8/v9+7n/WuWq4k+1qyZBuMsS3beGExDmsMhKQlYcuEUNJpM8FhpiXAlLSknWbatJOWlJCWlDZtM10gpbRJaFlKg0kwq41l402WLelKuvs923ve9bf0j/ec4yvwBthGCfrNaM65ukfn3ver53m+3+f7POcV1lp+TM6HgH8AdIGjwKeAzwLfAYof9E3ljwl4c0AbQKlyKkn61xqjfgP4U+DfADcDznkAn/gMgK8AZ7CWQb9DFvd9Y9Ru4K3Ax4G3AOI8gI9/cuDTwK87rnfGES69/iZFnmCN8YH9wL8CrjsP4BOfEvgjIcTtjdY0SZKSDnuUZYK1BmBqlMrnAXyKVP7dIIy6nh8xjAeUxRClCsACXArsOw/gEx8LHBFCHG82WwyHMWWWY0yOMQrgSuDweQCf/CTAXVG9SVGW9Ps9dJmhVQbWLAI/BcyfB/CJjwLu9f3QRkGd4bBPNhxSFglaF1PAG4GbzgP45Oc+KeUgrNUxWpEWKaosxqw8BbwZuOg8gE981oC+6/loBKoowVqUyrBVLbwKuP48gE987gTWhONglEEVJaU2YCxKZ2DtIvBuwDsP4JMegdaKIs9QeYE2miJPMVb7IzY+D+ATnB2Ag7VYa7GAQWGMpihSjFIA/tPB58cVwLcBu7RWSClwPA+jDQKDLguUKhi5VOY8gN972iOCaKTpEITEkZURU5aGLM8oihQqAP3zAH7vuQS41RhTH8Z9iqJASEBKlDGooiAd9rFWAzTPA/j4nUhdKcVwOCBLE+J+jFYlnitASFSZo415Wm/24wigBzCMu5gyQzqCeDigyHJA4DgOnueOy19+HsCzTw24BqDX3UQbjet5eIEPjpgAaAGrDWCTp3pD98cMwFng9Vma0Ott4vk+jnTwXB/f9REWpJRoo6nEzVOfHycAZ0by5fD62mnifhfHkQRhgO/5aKPJ8wSEAGuqx6dh8Z9rADpUM4pXA7cADaCpVGlc14uBPvBtYBn44ujru0e1qjtyWsapmmx7fj2VXf/uJBkunDl1nCLPcFyXeqOJ7/tgLUmWEQQ+rpCICruAyoT9SwHgG4C/C7xk+1+unD7Jt7/8WXnohYdbi5dc2ZJS7jXGYIz5pfFIVkqJEAIpZQEcBx4Cvjl6iyZwA7BojFlYOvkwg3gL15G4jsAqVdGFtRitKAqBDAMqbSOCvwwROAu8H3jn1tbWzMrqKoHvs2/vXpJBl9s/8yke/Naf0e2sk5QOpTYopVBKbQcO3/fxfd8PguBQvV4/VK/XXxeGIY7jYJRCW8P62jKb66fRpSL0avhhiB+FAGhrybIc182p1eq4jhyL7tPnMoAXAP8CeN3W1pZ/++23k2U5uxd28/B999JdPcGpM6uEC89nYOo88NBRXNdFiIotHcdBSjn5I4RACAFC4LkujUaDVqvFg0fupLu8RBC5tOZn0UajrSXwAwLPR0hJoRRlmROGdTzPAyGLUQqfsxHYBH4LeB3gL51aIk1TOltbdDubpElM6Ac0d+7DDyPCMMJ1XaSUeJ43eT7xVaqij9YarTV5ljEcDlldXWW90+PY0aMknVPsW1hgx75FahfuIy8KEDGudHF8H99zcaTEcXxArIxq7TkJoA+8aUQWPkBnfZW11WXiOKHZbFBvtAnCECklQRDgeR6O4+C6Lq7r4jgOQsqREaomqeyMvl+VNUtZlszv3E1UfxmnH3mQYw/ezfLKGZLuQfZdfAnB9DztmTnqGKIwQgCuFyKEeADYOFcBvAT42xury1Mbq6dZWz7Nd47cDV6T6ZkZwBLValx22WUsLVWRGYQBQggeb5dneyRiLUJKXNfFWovjOGhjCKMarfYUs7v2cv+Rb/KNO+9mfWWFA4eeR/uqa/FbNYwEKyReUEcI8c1trH5OAbgb+HvG6Cu++oVPc+yeb1EEbepzFzAzNY0xlna7zcbmJuvr63S6XcIgwBqLrhp8jDFVyo4ex/VvnMqOc/aaizv62vcbRBddzPTMLMeO7uPBO7/KxvqXyNKYy6+/kfbcPFGthe8FgDh5rgrpXwBeKYT0vbDO0Do0ZvbQaE3huj6DQR9rLasrKyydPMmePXuIpqYQUiAQk2jTWp8lYYQQeJ6HEGJCNGVZTsC21qK1xvVc5nfupt5qM79jge/c9XW++rVv0Ot0uP7lr+Dg1RciK8B75yKA1wHvBWastbitHbT3PI+o2cL1PKQUGGM4ceIEnuexa9cuZmZmkI7EddxJlG1P4+1Ajmuk53lnRaPjOBMwKyAVURhywUUHaE3NcOLYhZy8907Kz3+OHRc+j0Z7B0Iwcy4C+KJRS8UDDzzAmZV1omYLfxurTk9Po7WeMC1QpS8aIQTGGhzpTKJuO3CO4+D53iRSPd9HjqLPWkue5xMNqZTCaE2r3eJ5V1zDzoULeOTur/HQfUfYsfcigjB63ojgiqcLoPt0iuYzIJpZXl7myJEjKKWoRVHFqNuiZXyMMWitKzE8SkUhBMqoSSp7njcBUQiBNRYhq3T2PA/f99Fao5TCdV2U1pRFwXA4rN7faKTjsHvvPprtNkHgUypNUA3XLwWOPBWAF44u7EXACeAzzxJ4VwOHkzTh7rvvJklTojCcpJu1llGL9j1MO65hpSrBQhAEk9copTDbzE+lFL7v4zgOQRhUkamdxwAeSRsEJMMEytHP1oZGo4UQguXlZS688MJF3/d/DnjwyXxBCfyi0eW3+t3Njxpj/hvwMeA1z5J0OfTw0Yc5c+YMjpRI5zFC0EZPAFRKYbGTKMuyjMFggO/5tNttkiSpap8Y2U8j8VyWZeVIjCJZIJBCTiLRcRyiKKJWr9Fut6nVa7iuixlpyaIssdaytrbGqVOnprTWP/9UWIwElKXbWSEZ9htUaw0fB257BsF7PfDu/qC/eP8D95/1Da11VdiVnjyfMK3RFEVRXVxRlaLZ2VnSNCXLMqyxk/qmlJpEpdYaNSKXymF+rJ4WRQEWXMclCqPKiRlFrh3/51nLmTNnWFld2WuM+U3grz0ZgG3pVPVia2OZPE+bwMLIGfmJZ6jufQA4fOzhY/6gP5iwphQSi50Atz0VtdIUecFgMGB2dpYwDDl27Bh33nkna2trE2C1eUwbjqVKWZbkWVbJlhErj2tllmXVIEkIxKgtDEYgaq0x20rD0skl0e/3L7PW/kvg16y1848H4BEQZ2r1NlYVbKyewmg1diJufQYAvGxU+/xHH320ihIh8H3/saK/rcMYPx+nc5qmPPLII5w6dYqlpSXuueeeCUtro7GmqmljZt1eT7MsI47jyd+PmVo6sorcEUmJ0e8DoMqSsixxHIckSTh+/LgY9LsLeZq8l2oN+AXfTSIPAMfDqL7gBT5FMWRjdYm5nfua0nHfBNwL/LsfAsCDgH/m9Bn6/f6kl/V9f9JqjcEaR9J2ID3Po9frsbm5SVmWTE1NsWPHjrME9VjOmG2AKKXQWpNlGWVZ4nourlP9XM/zUFohssf+zTiCt/8uQgiWV5Z55L67ZKiHc8+/5qaf3nvw8gsc1/uHwJeA1B0Zj3/q+bUbvSDAkdDtrFFqLXbvWdwjpft3gHTEzr0fAMDd1lqWTp06S7sBOG7FjEVZ4LruJP3GBV9rTa1WQ0pJs9kEAWEQEkXRWf3vmHjKEQkEQTDpRDzPq1I6z/F9f9KlaK2rvnlblzJmdCmdESFZOhur9B+9j6gccB/Wn9934HCt4X145Ii/1/nQhz5kgBkhxJutMVhTYKyl393EGCuCIJp1XPdFVPskyQjE/GmC9xLgXcPhcPGeI0dGv5yc+HgTP088Joa/u4MY21e+7xNF0cSZEUKcFS1j0sGeHcXjlK5kkJp0JGVZUhQF2ahWFkVBURZYY8FW348766RnjhEUMUncZTNO0X7TKbWZrdfr17iu+8WxkL4TsFGtJYpsQBj4ZJ5Hd3OZIk9le2p+X2tq7hellC8HvgZ8Evi/TwPAA8Bir9ejPxgQBsFZOm+czmPmtdYSx/GEZLaL5+1AjN9j/Hz8Wsdx0EqT5/lE2hRFMSkX44gMgoAgCCapO45GPQIXIO2uk6+fxCtiur0u/bhLiCCLu5w4foyiLDl08KA3BnAJOOq43kJYazfi9R5RLaJIc5JhlyxN6Pc7UzOzu6+sN5pXCiHeBnwC+Kej+cPjnXdQfbRqcW1tFVWWGM+bgDWOuLHtLoQgz/MJY47rmBACi52k2bgls6OR2Xb2HRPBWF8aY1Bak6YZpVboUlGr10cpbFCqHKV+jlIF2hqyZEjRW8MZrGGSDuu9HitbXVpze5jesYuVRx9iKOpkWcH+iy4y21u5XwdeEdZab/H9WiOOt4hqdUySkKYxeRqTJ338qEmzNdVoNKbe6QfhLcCfAf9h9AjVZy1+YfS4eOcdX+bPv3oH9fbMpC0T8rE6GATBpH3TWtNoNKrhtrUTySGFPEszjmveODK3E4DjOLj45EWBEFAWKVZXu38C8ERGJnOk9Eiyqu7leU5Z5ORxn2L9JDLrMBz02epskOYlifW45NLDvPDqwzx8/z08/MCDXLj/AEKwbzuAnwA+IaWzOjWz65ezbDhTliVRVEMbhSoVqswpioxuZw0hHer19mKj2XpHFNXeIaSPM5ruW2PRRtHvd7njc3/M8ZVNLr/uJhgV7LEZMK5TtXp9vA01IY+iKCjLclLwy7I8K+W2A/jd6SnJkWVBnhcIq6qPMFiDIw15XqIHLkL6FCZEmYis9CgGHUx3FZN22epusdntIqVLs91A54LNXp/Tq5sUImTnzBSL+xZwHPfyx3Njfj0IG+HM3N7bOmsnfVUWRFGE9hUqL3GlROcpWxsbxP0tNjc8Ai9Euh55UWC0QekcKRzSYcL6yknKsmqrRpDhui5RFBGG4UToOlIShiHaGNKkGunmRYGQFZuOi/440saABkFAo9Fgx44drK6ukqcDpJeDKHBFgcBg7IixhabUJboEaz0KU5LmQ/QwgWRANuix3t1gECc4jsfUdJN2o0FQGI4/epQkzZlqTeMnfe7+4p8QYG9+XDtLCPHPGs3pS1SZvXpr7ZRfJCV+GOJ4o6GO8THWUOQ5RhmMNhhrSZMhxhqM1hSlwhQFxijKvByBZ3EcOamBY1a1xuCO9ZlSky7Ccyvttt00GJNDlmUEQUCr1aIoCprNJsPhkOV+BwkI1wISR4K1YrSo4ZJbH2mgVJI8LlCDHjZL6fc7rG9uUmhLPQppTzVpNep4niT0Pbb6XZYfPUbanmLeGeAONzl691cXn8gPXJPSeX97eqfUqnzl2sqSHw8GGCxRLcR1JM16g2GaIpyqyHuuSy4lVhk816MsNWmWTKKlWqUVZ7ki48eiKM7qV7V5LGXHtXJ7nzsGsNFsMDMzw7333stwOERrzWAwQEy3ESLEd/JJa2ZxEDhoI7BZgR4O0HHMsN9hs7PFeqeLcD12TNWZak9Rq4U4VEauRLLQjiizHmUnxplukemEoNGaejJD9X7H8d4zPbfntw3i1ctLj/i9bpeyqFGvh9TqdfyRLNFKIV2XIAyxRuO7PtJxKZMYx5HkeVoB9jhA5HmO67oVs45MgbIoKZVCK0VRFhPfbyJVRrVwZXmFZJiwvr4+atcsQRBSr9dxHBdrQoRDVTysg9YlathDDHuoNGZra4tOr8Py5oCZvfu58aU341rF4PRDSJ2DK7AGyrIyGGbrLp5bR6uMuQMHufTwTd0nA9ACD7uuf9vs3MLfB96WJPf6aTrEGEUYRPhBQJZlICzGWlzpYoSL47vYZEipDQIo8xJjNda6o4bdTFg2CIIJeEKIiesixs29NpPUHb9GjqZum6PBU1Hk6LIkjAJqtTpKlWS5RLsSFBhtIO8j0x467ZP0e3T6Hbr9IXlW4jam2bl4CTsuOIgrBflwiOo8CtpSFiV5XoCQ1Gs1smTIjv2HePHr3tbdsbDv956OpX/cdf3b5ub2KCm9N60vH5+KR6uxUjoURTGSDAJHiIpM8oIkzcjyEmsluqxSyRmn48iGKssSKeXEhxvLmTFR5HlOlmXkeX6W3qsmbD6NRmPC5DIMqdUbhKFfbduXOXkONs/wbYLMB8S9Dv24S68fU5YGxxXUGxH+3E4c1+Pe79xDGNYQWiILUWVAoQh8CQL6gz77r7iOG1/7M8XCvgN/7Ljebz3dmUjsuN5ts3O7HqjVmu/ZWDu1d2NtScRxH4EEaxgOhpTWEHoBQS1CImm32jiySjmrFY7nTSJruwkghaAYsawQYqL/iqKoNNqoQ9jeukkpaTQalSFqDMboUetXpRx5gcxjHJVQJAMGcY9ur0NSaFwpqNVqNOvVXkwn7nNq6QRh1KDWaFI3KcEwAQyR75HnisIUXHHzrVzz0lcV87v2fko6znuA7vczVMqkdH633mje5/n7399sz1y9vnKi2dlawxZltRKrNUk5wJgSz3XxmjW8KERoTV6oUVtWab1y1AVst5LGTX+WZWfZ+9/tkowfHcchDMOJ81JFtUKUGX45xKYxcdynP+gzGMYoBbV6RLMWUasFeK6LwFBXJctrZ+i5NcLAoS0VOxsetcAnTobU6xnXXXURl//EK4qp+X0fEUL+Y2DrB5nK5SD+j++HD0xN73hDrdb6ldn5rd2drZVGZ2OVYdwnyVKwCjvSfbVmHYSlyAtUWeL5VTtnlJnUtHFEja2noigw9jHJMi6K49c+toRgJ6ytdYkpMgKT4pcZw7hLt9thMByS5gWu9Gk2I1qNGkHk4okAz5UgoWkdCpXRT7dwS2jNT+MKS5z22XdBm+uu2suBG3/5SDBz4T8RQv6XZ2KseVxK+TthVPuSHwSvbrZmfmnn7sVdvW6n0eus0e2sU+QZ1mjqzQau46KKqsGXSqKlPqumjeXKWLIYY85KYWPMxHgFRiRk0NpQFgUqTxF5n0in6CxhMx6wtrnBVq+HsC7TUy0ajTr1WkjgObg4eBIcV1DmBqUstcChFU3jexKjS9xAc9U1V3L1y3+2O7tw6PPSCX/18ZaNfti58N1SOncHYe2/+kH0/Fqt9e65+d2XlWW+mKYJZZZSi9p85favoVR+1hB8nL5j5h2DM3ZOLJWkUbpqI7d7f0ZX7nOZ5YhiSJj3UVnCZr9Lr9dls9MlMwEXHrqKuekZ4o0lPKGRY0cHKI0hH1pKbXAdSTtqoVVGkQ1ZuGCKm1/5Unvw+jef8mtz/wj4w5En+r1NxzN8451ZYBH4gDH6BmP0QpFmfPIPPsbasGBmfhdyZCRst/DH9W+8ljE2E9I0JY7jkblp0MailUGVKSJPkGWMGg5Ihn22ej22+n3iOMGNZnj+NdezuP8ghw4d5J5vfIUT3/kGvjR4QmKQWFvtEwdRgBQOw6RP2Ghx3Q1X2hf/5Fv77R2LXxdCfnBkOD/hh0bEs3TnouZodvA+4JrN9dWFL//FHayureGOOpHxhukYzHHajq2ssW2VJAllmWOtQSmNzVN8nWCSAXE8oNPbotvtMsxVtReuLRddcS1XX/9SNja3qNfr9DpdTt37NVyVVU6QEERhgOMIijwnL1MuuvQqe+tb/kZ//6FLHpSO99vAnwDDH9VqxwC4A3gPcOvs/M4PXHTRRQsrKyuoEXGMHZUxgNtt9fFuvC4LSlWgygJTZHg6R5QJ2aDLemeLXqfPME8BSRSGBJ6LNVCkGSeWTtLrDXAdB2M0cZLg64J6rUYYBWAN3U6X+uwcr3/ruwY33fLah8Ko9jHgfwKrT/dCn83dGAs8Cvx7INq3b9977rrrroUkSSYAGmOqz2PYaq+vHMmRKqsNqlTYYohMh/g6I4tjNrbW6fT6DOIEKSRhUKcWONVykgCjYbC5TGoEju9jjSXpbCGzmEa7gedJhv0OWsCVL35V/FNve+fy/M6F3xFCfAZ45Pu9SPEc3Xxsl9b6o5///Od/+tixYxP5Mh5FaqWxVG2bNhqMwZQFUqXIMibp9hgMB2x1t+gOBmBdfDekFgp8z0V6DtJWlqKxliwtSJSgwEFbRdt3mapFCKsoVcKeiy/lTe+4Lb7siqv+reM4/xn4xg96Ye4TDNvNMwzguuM4f3Dg4otvPX78uL99lFhZ6gVGK4yxmDLF0zmeSsn7A7pxj7WtTTa7PfLCEtV8ZhtNfK+qZa4rwEq0qaIWIxA4eCh811AL64SuJMsGRO0p/vrP/S1e9po3fDmKah8G/vsPl2P2cQF0ngUANfD1PXsWBnNzc7PLy8soVQnmUinKIqPMcjyd4Osc8pj1Tp/1rU26w5hcQW16DzOtNjVhUMNNXEdWpVKDGaU8SIzReIFDsxVVw6F0SCk8bnjF63jjz7/r9Nz8rv8lhPg1IH4mLsx9gtr1bJytWlQ7fuDAgdkTJ4+TZwW6TDB5glMkyDRF5QO6/ZitQY9eGlOWAoTL4ZtuZv9ll3Pm9BkkivvvvANZpDju+Nc3YCuTNoh8PFcyzDKyImH/JVcUb/2btw0uvvTy+6SUHwT+YvQfyrMF4LO1I1gYYx5aW3r08NqjR/EcEGWOVDmmzOn3e/SHQwZpirUS12/SavgIaZnbtRPH9SnKkblgPVTWpR6OB/QuYRQSuII0y9noxMzv2cPb3/6+4vqX3PIt3w/+aLR1Fj/TF/WcbqgWeXb/w9+6A7ZOYX2PJE0YZgVp3GdYKhzpUY9aOIGDBELHxVjF+plTbHQS4ngwEtcpUWlRPjSaIfWoRpolrHcGNNpTxRve9q7Bq9/4s6ut9tR/HG2abTxb1/ScAuh63vHd+y9maekoa2trDPOSvNAkxsEVDnO1iCjycSUIW91LQxhBvHwC3YjJcsPmxjpl0mPHfIvpZhNlFBsbq3hhnZte9Ybi9W96+7d279n3OSHk730/eu4H1WnPLYCul7z2TW+n8Gt84X98kqboc+Glhzh09Q3c/v++QHzmJO2aREqBVgXWSiwSUxQUa0vEaUnNgV175vAcl063h/QlN7zq9dz6M2+NF/Yt/rmU8n1PtZb7TB0hxHO+ZL7ueB5Zrrnkyhex9O2v0pqexVhLVG+wIjy6cUKrFuI4AaVW5MZitSXwAubDsLrXVZoiQs21h+d4zZs/2F289PBR1/U+MpIlw+c0KJ5jAFOBwFjL6ZVVNrOCaH2dfikotcFKQTcpqoVHVc1GGlGEH3ikw5jBICGsC2588aW87JVv6O6//MX3eNHMx4UQnx2l63N+S+LnGsAjnucdvfGGGw6ePH6cM6drJL1NnCLH5CXFMMXROVY0aIYBQkryPGFjI6be8nnxLYe54abruvtf+JP3BPWZ3xdCfA5YfxZ06znXyk1+HvArSqnf2NzanN5YW+fT/+njPHzXN7GuIC0K2vUaDd9nkCYUWczs3v1ce91BrnrebPeCw++4J6zP/L6U8vNUd2D7kQH3o4pAC3zUdV27Y37HrzZrtYtJhoTRaFZiHLJkgJV1Fi97Ade+5Gaef/V1Z2Za7u1BbfojwonuFEJkP4pUPVcAHLd1/1oI8UhYq3/42ltuuTj+331UWbJnfjeXvOBKLnvBC9m7eIAwjI76QfjPBfwhQqTnEnA/qhT+7p77ZVmavLmztdkCdKPRtK7vC98PjjqO8x2qteJhNUtWCOGM76bxhM29HelHxPgTnOKvLIDbrtsA4numbme9phiAtAin+eQgjsqiLRPIV6C2DyGDv9oAPl3rCOzobhpP9/VmfPeN8wCeq8dd3tw4j8IPcf4/nqCzDatiX50AAAAASUVORK5CYII=" alt="SpaMo Logo"/>
        <h1>SpaMo</h1>
        <h2>Spaced Motif Analysis Tool</h2>
      </div>
      <p>
        For further information on how to interpret these results please access
        <a href="http://meme-suite.org/doc/spamo-output-format.html">http://meme-suite.org/doc/spamo-output-format.html</a>.<br> 
	To get a copy of the MEME software please access 
        <a href="http://meme-suite.org">http://meme-suite.org</a>.
      </p>
      <p>
        If you use SpaMo in your research please cite the following paper:<br />
        <span class="citation">
          Tom Whitington, Martin C. Frith, James Johnson and Timothy L. Bailey,
          "Inferring transcription factor complexes from ChIP-seq data",
          <i>Nucleic Acids Research</i>, <b>39</b>(15):e98, 2011.
          <a href="http://nar.oxfordjournals.org/content/39/15/e98">[full text]</a>
        </span>
      </p>
    </div>
    <!-- navigation -->
    <div class="pad2">
      <a class="jump" href="#primary_motifs">Primary Motifs</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump" href="#sequence_dbs">Sequence Database</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump" href="#secondary_dbs">Secondary Motif Databases</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump" href="#spacings">Spacing Analysis</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump" href="#inputs_sec">Inputs and Settings</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump" href="#program">Program information</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a
	class="jump" href="spamo.tsv">Results in TSV Format</a>&nbsp;<div class="help" data-topic="pop_results"></div>&nbsp;<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAQCAMAAAAyEe/dAAAACVBMVEX//wAAAAD////s2cV/AAAAdUlEQVQYlXVRBxLAIAhL+P+jC2HZhXcBZEWEldDsZcLIcAhHWWnK8SDcWQhMFUHdAQ1CqQ5+CWPmlHojl+nCJNRtzu4qRc3IUzmTVpXYK0nox0z0PI1stgchdK7lEv7ekhvalw8WW547Gyzedt/2/gLx8WXjXF/1AYFriNAWAAAAAElFTkSuQmCC" alt="NEW" id="new_1">&nbsp;&nbsp;|&nbsp;&nbsp;<a
        class="jump">Contributing Sequence IDs</a> <a class="jump" href="#" onclick="download_all_contr_seqs('txt');">[Download Plain]</a>&nbsp;<a class="jump" href="#" onclick="download_all_contr_seqs('bed');">[Download BED]</a>&nbsp;<div class="help" data-topic="pop_download_seqs"></div>&nbsp;<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAQCAMAAAAyEe/dAAAACVBMVEX//wAAAAD////s2cV/AAAAdUlEQVQYlXVRBxLAIAhL+P+jC2HZhXcBZEWEldDsZcLIcAhHWWnK8SDcWQhMFUHdAQ1CqQ5+CWPmlHojl+nCJNRtzu4qRc3IUzmTVpXYK0nox0z0PI1stgchdK7lEv7ekhvalw8WW547Gyzedt/2/gLx8WXjXF/1AYFriNAWAAAAAElFTkSuQmCC" alt="NEW" id="new_2" />
    </div>

    <div id="primary_motifs" class="header">
      <h2>Primary Motifs</h2>
      <span><a href="#sequence_dbs">Next</a> <a href="#">Top</a></span>
    </div>
    <div class="box">
      <table id="pri_tbl" class="th_ul">
        <thead>
          <tr>
            <th><span>Database <div class="help" data-topic="pop_primary_db"></div></span></th>
            <th><span>Name <div class="help" data-topic="pop_primary_name"></div></span></th>
            <th><span>Preview <div class="help" data-topic="pop_primary_preview"></div></span></th>
            <th><span>Significant Secondaries <div class="help" data-topic="pop_primary_nsig"></div></span></th>
            <th><span>List <div class="help" data-topic="pop_primary_sigs"></div></span></th>
          </tr>
        </thead>
        <tbody>
          <tr class="pri_row">
            <td class="pri_db"></td>
            <td class="pri_name"></td>
            <td><canvas class="pri_logo" height="50"></canvas></td>
            <td class="pri_nlist"></td>
            <td><div class="ml pri_list"></div></td>
          </tr>
        </tbody>
      </table>
      <h4>Alphabet</h4>
      <span id="bg_source"></span>
      <div id="alpha_bg" class="pad"></div>
      <script>
      {
        $("bg_source").appendChild(make_background_source("Background source", data.options['bgfile']));
        $("alpha_bg").appendChild(make_alpha_bg_table(spamo_alphabet));
      }
      </script>
    </div>
    <script>make_pri_table();</script>

    <div id="sequence_dbs" class="header">
      <h2>Sequence Database</h2>
      <span><a href="#secondary_dbs">Next</a> <a href="#primary_motifs">Previous</a> <a href="#">Top</a></span>
    </div>
    <div class="box">
      <table id="seq_tbl" class="th_ul">
        <thead>
          <tr>
            <th><span>Name <div class="help" data-topic="pop_seqs_name"></div></span></th>
            <th><span>Last Modified <div class="help" data-topic="pop_seqs_last_modified"></div></span></th>
            <th><span>Contained <div class="help" data-topic="pop_seqs_loaded"></div></span></th>
            <th><span>Too Short <div class="help" data-topic="pop_seqs_too_short"></div></span></th>
            <th><span>Too Masked <div class="help" data-topic="pop_seqs_ambiguous"></div></span></th>
            <th><span>No Primary <div class="help" data-topic="pop_seqs_no_primary"></div></span></th>
            <th><span>Too Similar <div class="help" data-topic="pop_seqs_too_similar"></div></span></th>
            <th><span>Used <div class="help" data-topic="pop_seqs_used"></div></span></th>
          </tr>
        </thead>
        <tbody>
          <tr class="seq_row">
            <td class="seq_name"></td>
            <td class="seq_last_modified"></td>
            <td class="seq_loaded"></td>
            <td class="seq_too_short"></td>
            <td class="seq_ambiguous"></td>
            <td class="seq_no_primary"></td>
            <td class="seq_too_similar"></td>
            <td class="seq_used"></td>
          </tr>
        </tbody>
      </table>
    </div>
    <script>make_seq_table();</script>

    <div id="secondary_dbs" class="header">
      <h2>Secondary Motif Databases</h2>
      <span><a href="#inputs_sec">Next</a> <a href="#sequence_dbs">Previous</a> <a href="#">Top</a></span>
    </div>
    <div class="box">
      <table id="sdb_tbl" class="th_ul">
        <thead>
          <tr>
            <th><span>Name <div class="help" data-topic="pop_sdb_name"></div></span></th>
            <th><span>Last Modified <div class="help" data-topic="pop_sdb_last_modified"></div></span></th>
            <th><span>Number of Motifs <div class="help" data-topic="pop_sdb_num_motifs"></div></span></th>
            <th><span>Motifs Significant <div class="help" data-topic="pop_sdb_sig_motifs"></div></span></th>
            <th><span>Motifs Redundant <div class="help" data-topic="pop_sdb_redundant_motifs"></div></span></th>
          </tr>
        </thead>
        <tbody>
          <tr class="sdb_row">
            <td class="sdb_name"></td>
            <td class="sdb_last_modified"></td>
            <td class="sdb_loaded"></td>
            <td class="sdb_sig_motifs"></td>
            <td class="sdb_redundant_motifs"></td>
          </tr>
        </tbody>
      </table>
    </div>
    <script>make_sdb_table();</script>

    <div id="inputs_sec" class="header">
      <h2>Settings</h2>
      <span><a href="#spacings">Next</a> <a href="#secondary_dbs">Previous</a> <a href="#">Top</a></span>
    </div>
    <div class="box">
      <table id="tbl_settings" class="inputs hide_advanced">
	<tr>
	  <th>Match Score Threshold <div class="help" data-topic="pop_sa_minscore"></div> &nbsp;&nbsp;</th>
	  <td id="opt_minscore"></td>
	</tr>
	<tr>
	  <th>Margin size <div class="help" data-topic="pop_sa_margin"></div> </th>
	  <td id="opt_margin"></td>
	</tr>
	<tr class="advanced">
	  <th>Width of histogram bins</th>
	  <td id="opt_bin"></td>
	</tr>
	<tr class="advanced">
	  <th>Significance computed up to this distance &nbsp;&nbsp;</th>
	  <td id="opt_range"></td>
	</tr>
	<tr class="advanced">
	  <th>Secondary match handling</th>
	  <td id="opt_usebestsec">
	    <span class="sec_best">Count only the best secondary match above the score threshold</span>
	    <span class="sec_all">Count all secondary matches above the match score threshold</span>
	  </td>
	</tr>
	<tr class="advanced">
	  <th>Maximum allowed sequence identity</th>
	  <td id="opt_shared"></td>
	</tr>
	<tr class="advanced">
	  <th>Odds ratio for redundancy heuristic</th>
	  <td id="opt_odds"></td>
	</tr>
	<tr class="advanced">
	  <th>Bin <i>p</i>-value cutoff</th>
	  <td id="opt_cutoff"></td>
	</tr>
	<tr class="advanced">
	  <th>Secondary motif <i>E</i>-value cutoff</th>
	  <td id="opt_evalue"></td>
	</tr>
	<tr class="advanced">
	  <th>Overlapping bases for redundancy check</th>
	  <td id="opt_overlap"></td>
	</tr>
	<tr class="advanced">
	  <th>Fraction of sites for redundancy check</th>
	  <td id="opt_joint"></td>
	</tr>
	<tr class="advanced">
	  <th>Pseudocount added to motifs</th>
	  <td id="opt_pseudo"></td>
	</tr>
	<tr class="advanced">
	  <th>Bit threshold for trimming motif edges</th>
	  <td id="opt_trim"></td>
	</tr>
	<tr class="advanced">
	  <th>Primary and secondary motif alphabets</th>
	  <td id="opt_xalph">
	    <span class="xalph_true">Converting secondary alphabet to primary alphabet</span>
	    <span class="xalph_false">Primary and secondary alphabets must match</span>
	  </td>
	</tr>
	<tr class="advanced">
	  <th>Random number seed</th>
	  <td id="opt_numgen"></td>
	</tr>
	<tr>
	  <td colspan="2" style="text-align: center">
	    <a href="javascript:toggle_class(document.getElementById('tbl_settings'), 'hide_advanced')">
	      <span class="show_more">Show Advanced Settings</span>
	      <span class="show_less">Hide Advanced Settings</span>
	    </a>
	  </td>
	</tr>
      </table>
      <script>
      {
	$("opt_minscore").textContent = (data.options.seq_min_hit_score >= 0)
          ? data.options.seq_min_hit_score + " (bits)"
          : -100 * data.options.seq_min_hit_score + "% of the maximum possible match score for the motif";
	$("opt_margin").textContent = data.options.margin;
	$("opt_bin").textContent = data.options.bin_size;
	$("opt_range").textContent = data.options.bin_pvalue_calc_range;
	$("opt_usebestsec").className = data.options.use_best_secondary ? "best" : "all";
	$("opt_shared").textContent = data.options.seq_max_shared_fract;
	$("opt_odds").textContent = data.options.seq_odds_ratio;
	$("opt_cutoff").textContent = data.options.bin_pvalue_cutoff;
	$("opt_evalue").textContent = data.options.motif_evalue_cutoff;
	$("opt_overlap").textContent = data.options.redundant_overlap;
	$("opt_joint").textContent = data.options.redundant_joint;
	$("opt_pseudo").textContent = data.options.motif_pseudocount;
	$("opt_trim").textContent = data.options.motif_trim;
	$("opt_xalph").className = data.options.xalph ? "convert" : "no_convert";
	$("opt_numgen").textContent = data.options.seed;
      }
      </script>
    </div>

    <div id="spacings">
      <div id="tmpl_primary">
        <div class="header">
          <h2>Spacing Analysis for <span class="sa_primary_name"></span></h2>
          <span><a href="#program">Next</a> <a href="#inputs_sec">Previous</a> <a href="#">Top</a></span>
        </div>
        <div class="box">
          <div>
            <table class="sa_summary">
              <tr>
                <th>Secondary Motif:</th>
                <td><span class="sa_name"></span> <div class="help" data-topic="pop_sa_name"></div></td>
                <th>Cluster:</th>
                <td><span class="sa_cluster"></span> <div class="help" data-topic="pop_sa_cluster"></div></td>
                <th>E-value:</th>
                <td><span class="sa_evalue"></span> <div class="help" data-topic="pop_sa_evalue"></div></td>
                <th>Best Gap:</th>
                <td><span class="sa_gap"></span> <div class="help" data-topic="pop_sa_gap"></div></td>
                <th>Best Orientation:</th>
                <td><span class="sa_orient"></span> <div class="help" data-topic="pop_sa_orient"></div></td>
              </tr>
            </table>
          </div>
          <div class="sp_info">
            <h3>Primary Motif Logo <div class="help" data-topic="pop_primary"></div></h3>
            <div><canvas class="sa_plogo_in" height="100" width="0"></canvas></div>
          </div>
          <div class="sp_info">
            <h3>Secondary Motif Logo <div class="help" data-topic="pop_secondary"></div></h3>
            <div><canvas class="sa_slogo_in" height="100" width="0"></canvas></div>
            <h3>Inferred Secondary Motif Logo <div class="help" data-topic="pop_inferred_secondary"></div></h3>
            <div>
              <div class="overlink">
                <span class="top">
                  <a href="#" class="sa_slogo_out_eps">Download as EPS</a>
                </span>
                <span class="base">
                  <a href="#" class="sa_slogo_out_meme">Download as MEME motif</a>
                </span>
                <canvas class="sa_slogo_out" height="100" width="0"></canvas>
              </div>
            </div>
            <div style="text-align:center"><a href="#" class="sa_slogo_eps">Download as EPS</a></div>
          </div>
          <div class="sp_info" style="min-width:100px;">
            <table class="th_ul">
              <thead>
                <tr>
                  <th></th>
                  <th><span class="th_ul">Alignment Logo<div class="help" data-topic="pop_alignment_logos"></div></span></th>
                </tr>
              </thead>
              <tbody>
                <tr>
                  <th>Up<span class="revcomp">+</span></th>
                  <td>
                    <div class="overlink">
                      <span class="top"><a class="sa_align_up_pos_eps">Download as EPS</a></span>
                      <span class="base"><a class="sa_align_up_pos_meme">Download as MEME motif</a></span>
                      <div class="sa_scroll_up_pos">
                        <canvas class="sa_align_up_pos" height="40"></canvas>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="revcomp">
                  <th>Up-</th>
                  <td>
                    <div class="overlink">
                      <span class="top"><a class="sa_align_up_neg_eps">Download as EPS</a></span>
                      <span class="base"><a class="sa_align_up_neg_meme">Download as MEME motif</a></span>
                      <div class="sa_scroll_up_neg">
                        <canvas class="sa_align_up_neg" height="40"></canvas>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr>
                  <th style="white-space:nowrap">Dn<span class="revcomp">+</span></th>
                  <td>
                    <div class="overlink">
                      <span class="top"><a class="sa_align_dn_pos_eps">Download as EPS</a></span>
                      <span class="base"><a class="sa_align_dn_pos_meme">Download as MEME motif</a></span>
                      <div class="sa_scroll_dn_pos">
                        <canvas class="sa_align_dn_pos" height="40"></canvas>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr class="revcomp">
                  <th>Dn-</th>
                  <td>
                    <div class="overlink">
                      <span class="top"><a class="sa_align_dn_neg_eps">Download as EPS</a></span>
                      <span class="base"><a class="sa_align_dn_neg_meme">Download as MEME motif</a></span>
                      <div class="sa_scroll_dn_neg">
                        <canvas class="sa_align_dn_neg" height="40"></canvas>
                      </div>
                    </div>
                  </td>
                </tr>
                <tr>
                  <td>&nbsp;</td>
                  <td>
                    <div class="sa_align_scrollbox">
                      <div class="sa_align_scroll"></div>
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
            <div style="text-align:center"><a href="#" class="sa_align_eps">Download as EPS</a></div>
          </div>
          <br>
          <div class="sp_info">
            <h3>Spacings <div class="help" data-topic="pop_spacings"></div></h3>
            <div class="sa_spacings_anchor">
              <div class="sa_spacings_scroll">
                <table class="sa_spacings th_ul nowrap">
                  <thead>
                    <tr class="fixed_header">
                      <th>
                        Gap
                        <div class="fill_back"></div>
                        <div>Gap</div>
                      </th>
                      <th>
                        Orientation
                        <div>Orientation</div>
                      </th>
                      <th>
                        <i>p</i>-value
                        <div><i>p</i>-value</div>
                      </th>
                    </tr>
                  </thead>
                </table>
              </div>
            </div>
            Highlight:
            <input type="checkbox" class="sa_hl_sel_chk" checked>
            <label class="sa_hl_sel_lbl">Selected</label>
            <input type="checkbox" class="sa_hl_all_chk" checked>
            <label class="sa_hl_all_lbl">All</label>
          </div>
          <div class="sp_info">
            <h3>Overview Graph <div class="help" data-topic="pop_histogram"></div></h3>
            <canvas class="sa_overview_graph" width="350" height="300"></canvas>
            <div style="text-align:center"><a href="#" class="sa_overview_graph_dl">Download as EPS</a></div>
          </div>
          <div class="sp_info">
            <h3>Selected Orientation Graph <div class="help" data-topic="pop_selected_orient"></div></h3>
            <canvas class="sa_selected_graph" width="400" height="300"></canvas>
            <div style="text-align:center"><a href="#" class="sa_selected_graph_dl">Download as EPS</a></div>
          </div>
          <div class="sp_info">
            <h3>Contributing Sequence IDs (<span class="sa_contr_seq_count"></span>) <div class="help" data-topic="pop_seq_ids"></div></h3>
            <textarea class="sa_contr_seqs" cols="30" wrap="off" readonly></textarea>
            <br>
            <select class="sa_contr_seqs_format">
              <option value="0">Plain Format</option>
              <option value="1">BED Format</option>
            </select>
            <a href="#" class="sa_contr_seqs_dl">Download</a>
          </div>
          <h3>Secondaries <div class="help" data-topic="pop_secondaries"></div></h3>
          <div>
            <div class="box" style="float:right">
              <h4>Filter <div class="help" data-topic="pop_filter"></div></h4>
              <div>
                <input type="checkbox" class="sa_filter_chk_top">
                <label class="sa_filter_lbl_top">Top</label>
                <input type="number" class="sa_filter_ipt_top" size="4" value="10" min="0" step="1" disabled>
              </div>
              <div>
                <input type="checkbox" class="sa_filter_chk_id">
                <label class="sa_filter_lbl_id">ID matches</label>
                <input type="text" class="sa_filter_ipt_id" size="12" value=".*" disabled>
              </div>
              <div>
                <input type="checkbox" class="sa_filter_chk_name">
                <label class="sa_filter_lbl_name">Name matches</label>
                <input type="text" class="sa_filter_ipt_name" size="12" value=".*" disabled>
              </div>
              <div>
                <input type="checkbox" class="sa_filter_chk_cluster">
                <label class="sa_filter_lbl_cluster">Cluster matches</label>
                <input type="text" class="sa_filter_ipt_cluster" size="12" value=".*" disabled>
              </div>
              <div>
                <input type="checkbox" class="sa_filter_chk_ev">
                <label class="sa_filter_lbl_ev"><i>E</i>-value &le;</label>
                <input type="text" class="sa_filter_ipt_ev" size="4" value="1" disabled>
              </div>
              <div>
                <input type="checkbox" class="sa_filter_chk_pv_gap">
                <label class="sa_filter_lbl_gap">Gaps (ranges allowed)</label>
                <input type="text" class="sa_filter_ipt_gap" size="4" value="0-10" disabled>
              </div>

              <h4>Sort <div class="help" data-topic="pop_sort"></div></h4>
              <div>
                <label class="sa_sort_lbl">Sort by</label>
                <select class="sa_sort">
                  <option value="0">ID</option>
                  <option value="1">Name</option>
                  <option value="2">Cluster</option>
                  <option value="3" selected>E-value</option>
                  <option value="4">Gap</option>
                  <option value="5">Orientation</option>
                  <option value="6">Spacing</option>
                </select>
              </div>
              
              <input type="button" class="sa_update_filter_sort" value="Update">
            </div>
            <table class="sa_table th_ul">
              <thead>
                <tr>
                  <th><span class="th_ul">Lock <div class="help" data-topic="pop_sa_lock"></div></span></th>
                  <th><span class="th_ul">ID <div class="help" data-topic="pop_sa_id"></div></span></th>
                  <th><span class="th_ul">Name <div class="help" data-topic="pop_sa_alt"></div></span></th>
                  <th><span class="th_ul">Cluster <div class="help" data-topic="pop_sa_cluster"></div></span></th>
                  <th><span class="th_ul">E-value <div class="help" data-topic="pop_sa_evalue"></div></span></th>
                  <th><span class="th_ul">Best Gap <div class="help" data-topic="pop_sa_gap"></div></span></th>
                  <th><span class="th_ul">Best Orientation <div class="help" data-topic="pop_sa_orient"></div></span></th>
                  <th><span class="th_ul">Spacings <div class="help" data-topic="pop_sa_spacings"></div></span></th>
                </tr>
              </thead>
            </table>
          </div>
        </div>
      </div>
    </div>
    <script>make_spacing_analysis();</script>

    <div id="program" class="bar">
      <div style="text-align:right;"><a href="#spacings">Previous</a> <a href="#">Top</a></div>
      <div class="subsection">
        <h5>SpaMo version</h5>
        <span id="version"></span> (Release date: <span id="release"></span>)
      </div>
      <div class="subsection">
        <h5>Reference</h5>
        <span class="citation">
          Tom Whitington, Martin C. Frith, James Johnson and Timothy L. Bailey,
          "Inferring transcription factor complexes from ChIP-seq data",
          <i>Nucleic Acids Research</i>, <b>39</b>(15):e98, 2011.
        </span>
      </div>
      <div class="subsection">
        <h5>Command line</h5>
        <textarea id="cmd" rows="1" style="width:100%;" readonly>asdf</textarea><br>
        <br>Result calculation took <span id="runtime"></span> seconds<br>
      </div>
    </div>
    <script>make_program_summary();</script>
  </body>
</html>
